ZetCode

Python sqlite3.Connection.commit 方法

上次修改时间:2025 年 4 月 15 日

这个综合指南探索了 Python 的 sqlite3.Connection.commit 方法,它用于最终确定数据库事务。 我们将介绍它的用途、使用模式和实际示例,并进行适当的资源管理。

基本定义

commit 方法会最终确定 SQLite 数据库中的当前事务。 它使自上次提交或回滚以来的所有更改永久生效。

关键特征:它在连接对象上调用,将更改写入磁盘,并结束当前事务。 如果不提交,更改仅对当前连接可见。

基本提交示例

此示例展示了 commit 的基本用法,以将更改保存到数据库。 我们将创建一个表并插入一些数据。

basic_commit.py
import sqlite3

with sqlite3.connect('test.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS products
                     (id INTEGER PRIMARY KEY, name TEXT, price REAL)''')
    
    cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)", 
                  ('Laptop', 999.99))
    
    # Explicitly commit the transaction
    conn.commit()
    
    print("Transaction committed successfully")

commit 调用使表创建和数据插入永久生效。 如果没有它,更改将在连接关闭时丢失。

请注意,我们使用 with 语句来确保连接已正确关闭,即使在执行过程中发生错误也是如此。

自动提交模式示例

SQLite 可以在自动提交模式下运行,其中每个语句都会自动提交。 此示例演示如何启用它。

autocommit.py
import sqlite3

# Set isolation_level=None for autocommit mode
with sqlite3.connect('autocommit.db', isolation_level=None) as conn:
    cursor = conn.cursor()
    
    # No need for explicit commit - each statement is committed immediately
    cursor.execute("CREATE TABLE IF NOT EXISTS logs (message TEXT, created_at TIMESTAMP)")
    cursor.execute("INSERT INTO logs VALUES ('System started', datetime('now'))")
    
    cursor.execute("SELECT * FROM logs")
    print(cursor.fetchall())

在自动提交模式 (isolation_level=None) 中,每个 SQL 语句都被视为一个单独的事务。 这对于简单的脚本很有用。

在复杂操作中使用自动提交时要小心,因为它不为多个语句提供原子性。

单个事务中的多个操作

此示例展示了如何在单个事务中将多个操作分组,并使用显式提交。

multi_operation.py
import sqlite3

with sqlite3.connect('bank.db') as conn:
    try:
        cursor = conn.cursor()
        
        # Start transaction (implicit with first execute)
        cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
        cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
        
        # Commit both updates together
        conn.commit()
        print("Funds transferred successfully")
        
    except sqlite3.Error as e:
        print("Transaction failed:", e)
        conn.rollback()

两个 UPDATE 语句都是同一事务的一部分。 它们要么都成功(提交时),要么都失败(如果发生回滚)。

这种原子性对于金融交易等操作至关重要,因为部分更新会导致数据不一致。

使用上下文管理器进行提交

Python 的上下文管理器可以自动处理提交。 此示例展示了连接和游标作为上下文管理器。

context_managers.py
import sqlite3
from contextlib import closing

with sqlite3.connect('inventory.db') as conn:
    with closing(conn.cursor()) as cursor:
        cursor.execute('''CREATE TABLE IF NOT EXISTS items
                         (id INTEGER PRIMARY KEY, name TEXT, quantity INTEGER)''')
        cursor.executemany("INSERT INTO items (name, quantity) VALUES (?, ?)",
                          [('Hammer', 10), ('Nails', 500), ('Screwdriver', 8)])
        
        # No explicit commit needed - connection context manager handles it
        print("Items added to inventory")

如果未发生异常,连接的上下文管理器会自动提交;如果发生错误,则回滚。 closing 包装器确保游标已正确关闭。

此模式减少了样板代码并确保了适当的资源清理。

频繁提交的性能影响

此示例演示了频繁提交和在单个事务中批量操作之间的性能差异。

performance.py
import sqlite3
import time

def time_operations(commit_frequency):
    with sqlite3.connect('perf_test.db') as conn:
        cursor = conn.cursor()
        cursor.execute("CREATE TABLE IF NOT EXISTS test_data (value INTEGER)")
        cursor.execute("DELETE FROM test_data")  # Clear previous data
        
        start = time.time()
        for i in range(1000):
            cursor.execute("INSERT INTO test_data VALUES (?)", (i,))
            if i % commit_frequency == 0:
                conn.commit()
        conn.commit()  # Final commit
        return time.time() - start

# Test different commit frequencies
print("Commit every 1 row:", time_operations(1), "seconds")
print("Commit every 10 rows:", time_operations(10), "seconds")
print("Commit every 100 rows:", time_operations(100), "seconds")

每个提交操作都有开销,因为 SQLite 必须将更改写入磁盘。 此示例展示了在较少的事务中批量操作如何提高性能。

在事务大小(性能)和原子性要求(数据一致性)之间找到正确的平衡。

使用保存点提交

SQLite 支持用于嵌套事务的保存点。 此示例展示了如何将它们与提交和回滚一起使用。

savepoints.py
import sqlite3

with sqlite3.connect('savepoints.db') as conn:
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, description TEXT)")
    
    # Main transaction
    cursor.execute("INSERT INTO tasks (description) VALUES ('Main task')")
    
    # Create savepoint
    cursor.execute("SAVEPOINT checkpoint1")
    try:
        cursor.execute("INSERT INTO tasks (description) VALUES ('Optional subtask 1')")
        cursor.execute("INSERT INTO tasks (description) VALUES ('Optional subtask 2')")
        
        # Commit just the savepoint changes
        cursor.execute("RELEASE SAVEPOINT checkpoint1")
        print("Savepoint changes committed")
        
    except:
        # Rollback only the savepoint changes
        cursor.execute("ROLLBACK TO SAVEPOINT checkpoint1")
        print("Savepoint changes rolled back")
    
    # Commit the main transaction
    conn.commit()
    print("Main transaction committed")

保存点允许在较大的事务中进行部分回滚。 RELEASE 语句仅提交保存点的更改,而主事务仍需要显式提交。

这对于某些部分是可选的或可能失败而不需要完全回滚的复杂操作很有用。

在多线程环境中提交

此示例演示了当多个线程访问同一数据库时,正确的提交用法。

threading.py
import sqlite3
import threading

def worker(worker_id):
    # Each thread gets its own connection
    with sqlite3.connect('threaded.db', timeout=10.0) as conn:
        cursor = conn.cursor()
        cursor.execute("INSERT INTO thread_log (thread_id, action) VALUES (?, ?)",
                      (worker_id, f"Started work {worker_id}"))
        
        # Simulate work
        import random
        import time
        time.sleep(random.random())
        
        cursor.execute("UPDATE counters SET value = value + 1 WHERE id = 1")
        conn.commit()
        print(f"Thread {worker_id} committed changes")

# Initialize database
with sqlite3.connect('threaded.db') as conn:
    conn.execute("CREATE TABLE IF NOT EXISTS thread_log (thread_id INTEGER, action TEXT)")
    conn.execute("CREATE TABLE IF NOT EXISTS counters (id INTEGER PRIMARY KEY, value INTEGER)")
    conn.execute("INSERT OR IGNORE INTO counters VALUES (1, 0)")
    conn.commit()

# Start multiple threads
threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

每个线程都必须有自己的连接并单独管理其事务。 如果数据库被锁定,timeout 参数可以防止无限期等待。

在多线程场景中,正确的提交处理至关重要,以防止死锁并确保跨线程的数据一致性。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程