ZetCode

Python sqlite3.Connection.total_changes 属性

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

本综合指南探讨 Python 的 sqlite3.Connection.total_changes 属性,该属性跟踪自连接以来数据库行修改的总数。我们将介绍它的行为、使用模式和实际示例。

基本定义

SQLite 连接对象的 total_changes 属性返回自数据库连接打开以来已修改、插入或删除的数据库行的总数。

主要特点:它计算所有更改,包括来自触发器的更改;它是连接特定的;并且计数器在连接关闭时重置。 这与仅显示上次操作的 changes 不同。

total_changes 的基本用法

此示例演示了 total_changes 的基本用法,用于跟踪通过连接所做的修改。

basic_usage.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE test (id INTEGER, data TEXT)')
    
    # Initial changes (table creation counts)
    print(f"Initial changes: {conn.total_changes}")
    
    cursor.execute("INSERT INTO test VALUES (1, 'First')")
    cursor.execute("INSERT INTO test VALUES (2, 'Second')")
    
    # Changes after inserts
    print(f"After inserts: {conn.total_changes}")
    
    cursor.execute("UPDATE test SET data = 'Updated' WHERE id = 1")
    
    # Changes after update
    print(f"After update: {conn.total_changes}")

此示例创建一个内存数据库,进行多次修改,并在每次操作后打印更改计数。 表创建在 SQLite 中也算作更改。

输出将显示递增的数字,因为每个操作都会影响数据库。 这是监控总体数据库活动的最简单方法。

跟踪事务内的更改

此示例显示了 total_changes 在事务中的行为,包括回滚。

transaction_changes.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)')
    
    print(f"After create: {conn.total_changes}")
    
    # First transaction
    cursor.execute("INSERT INTO items (name) VALUES ('Apple')")
    cursor.execute("INSERT INTO items (name) VALUES ('Banana')")
    conn.commit()
    print(f"After commit: {conn.total_changes}")
    
    # Second transaction (rolled back)
    cursor.execute("INSERT INTO items (name) VALUES ('Cherry')")
    print(f"Before rollback: {conn.total_changes}")
    conn.rollback()
    print(f"After rollback: {conn.total_changes}")

这表明 total_changes 包括未提交的更改,直到发生回滚。 计数器随着每次操作而增加,但在回滚更改时减少。

回滚将计数器恢复到事务前的状态,表明 total_changes 反映了数据库的实际持久状态。

比较 total_changes 和 changes

此示例将 total_changes 与仅显示上次操作影响的 changes 属性进行对比。

changes_comparison.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE logs (id INTEGER, message TEXT)')
    
    print(f"Total changes: {conn.total_changes}")
    print(f"Last changes: {conn.changes}")
    
    cursor.executemany("INSERT INTO logs VALUES (?, ?)", 
                      [(1, 'start'), (2, 'middle'), (3, 'end')])
    
    print(f"After multi-insert - Total: {conn.total_changes}")
    print(f"After multi-insert - Last: {conn.changes}")
    
    cursor.execute("DELETE FROM logs WHERE id IN (1, 3)")
    
    print(f"After delete - Total: {conn.total_changes}")
    print(f"After delete - Last: {conn.changes}")

主要区别在于 changes 仅显示最近操作的行数,而 total_changes 累积所有更改。 这在多行操作中尤其明显。

changes 属性对于立即反馈很有用,而 total_changes 提供了一个连接范围的活动指标。

触发器对 total_changes 的影响

此示例演示了触发器如何影响 total_changes 计数,因为它们执行的额外操作会被计数。

trigger_impact.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    
    # Create tables and trigger
    cursor.execute('CREATE TABLE orders (id INTEGER, amount REAL)')
    cursor.execute('CREATE TABLE audit (order_id INTEGER, change TEXT)')
    cursor.execute('''CREATE TRIGGER log_order AFTER INSERT ON orders
                     BEGIN
                         INSERT INTO audit VALUES (NEW.id, 'Order created');
                     END''')
    
    print(f"After setup: {conn.total_changes}")
    
    # Insert that fires the trigger
    cursor.execute("INSERT INTO orders VALUES (1, 99.99)")
    print(f"After insert: {conn.total_changes}")
    
    # Verify both tables were modified
    print("Orders:", cursor.execute("SELECT * FROM orders").fetchall())
    print("Audit:", cursor.execute("SELECT * FROM audit").fetchall())

触发器导致将额外插入到审计表中,这意味着单个 INSERT 操作会导致两个计数更改。 这表明 total_changes 如何捕获所有数据库修改。

当存在触发器时,了解此行为非常重要,因为更改计数可能高于仅来自直接操作的预期。

监控批量操作

此示例显示了如何使用 total_changes 来监控大型批量操作并提供进度反馈。

bulk_operations.py
import sqlite3
import random

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE readings (id INTEGER, value REAL)')
    
    # Insert 1000 random readings
    for i in range(1, 1001):
        cursor.execute("INSERT INTO readings VALUES (?, ?)", 
                      (i, random.uniform(0, 100)))
        if i % 100 == 0:
            print(f"Inserted {i} rows, total changes: {conn.total_changes}")
    
    # Delete half the records
    cursor.execute("DELETE FROM readings WHERE id % 2 = 0")
    print(f"After delete: {conn.total_changes}")

这种模式对于需要提供进度更新的长时间运行的操作非常有用。 total_changes 提供了迄今为止所有修改的准确计数。

该示例还表明,删除的计数方式与插入相同,删除的每一行都会递增计数器。

重置 total_changes

虽然 total_changes 无法直接重置,但此示例显示了如何通过使用单独的连接来有效地创建新基线。

resetting_changes.py
import sqlite3

# First connection with initial changes
with sqlite3.connect('test.db') as conn1:
    cursor = conn1.cursor()
    cursor.execute('CREATE TABLE IF NOT EXISTS temp (id INTEGER)')
    cursor.execute('INSERT INTO temp VALUES (1)')
    print(f"Connection 1 changes: {conn1.total_changes}")
    
# Second connection starts fresh
with sqlite3.connect('test.db') as conn2:
    cursor = conn2.cursor()
    print(f"Connection 2 initial: {conn2.total_changes}")
    cursor.execute('INSERT INTO temp VALUES (2)')
    print(f"Connection 2 after insert: {conn2.total_changes}")

每个新连接都以 total_changes 计数为零开始。 此示例显示了如何在需要时通过创建新连接来有效地“重置”跟踪。

当您希望分别监控应用程序执行的特定阶段中的更改时,此方法很有用。

性能注意事项

此示例表明,访问 total_changes 对性能的影响最小,可以安全地频繁使用。

performance_test.py
import sqlite3
import time

with sqlite3.connect(':memory:') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE test (id INTEGER)')
    
    start_time = time.time()
    for i in range(10000):
        cursor.execute("INSERT INTO test VALUES (?)", (i,))
        changes = conn.total_changes  # Access in each iteration
    duration = time.time() - start_time
    
    print(f"Inserted {conn.total_changes} rows in {duration:.3f} seconds")
    print(f"Average time per insert: {(duration*1000)/10000:.3f} ms")

total_changes 计数器由 SQLite 在内部维护,因此访问它非常高效。 此示例表明,频繁访问只会增加可忽略不计的开销。

这使得它可以在对性能敏感的代码中使用,在这些代码中您需要监控数据库活动而不会产生重大影响。

最佳实践

资料来源

作者

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

列出所有 Python 教程