SQLite progress_handler:查询怎样主动停下来,为什么回调次数不是秒数
先给长查询一个可观察的停止条件
交互式工具允许用户取消查询时,不能只把界面的加载图标关掉。数据库计算本身也要收到停止信号。Python 的 SQLite 连接可以注册进度回调,运行到检查点时读取一个轻量状态;回调返回非零值,当前操作就会被中断。它适合协作式取消,但不是按行数计算的完成百分比。
实验使用 Python 3.12.14 与 SQLite 3.53.1,仅建立内存数据库。先放入一百个整数,再执行两个表别名的笛卡尔积求和。我们不等待真实时间过去,而是在第三次回调时请求中断,这样停止条件容易复核,也不会把机器快慢混进结论。保存完整代码后运行 python demo.py。
注册、捕获和清理放在同一处
set_progress_handler 的第二个参数表示检查之间大约经历的虚拟机指令数量,不是每隔多少毫秒,也不是每执行几条 SQL。不同计划、SQLite 版本和操作阶段都会影响检查发生的位置。代码选用一百只是小实验参数,不应直接当作生产查询的统一预算。
回调只增加计数并返回布尔值,不执行其他 SQL。外层确认异常属于 SQLITE_INTERRUPT,避免把语法错误或其他数据库异常也误称为主动取消。无论查询正常完成还是失败,finally 都移除处理器,然后才开始新的查询,避免同一个取消状态误伤后续操作。
AI概念示意图:指令块经过检查点时遇到停止杆,清理后另一条路径仍可继续。图片只说明概念,不是运行截图。
import sqlite3
con = sqlite3.connect(":memory:")
calls = 0
def progress():
global calls
calls += 1
return calls >= 3
try:
con.execute("CREATE TABLE nums(n INTEGER NOT NULL)")
con.executemany("INSERT INTO nums VALUES (?)", [(n,) for n in range(100)])
con.commit()
con.set_progress_handler(progress, 100)
try:
con.execute("SELECT sum(a.n + b.n) FROM nums a CROSS JOIN nums b").fetchone()
except sqlite3.OperationalError as exc:
assert exc.sqlite_errorcode == sqlite3.SQLITE_INTERRUPT
print("cancelled:", exc.sqlite_errorname)
else:
raise AssertionError("query should be interrupted")
finally:
con.set_progress_handler(None, 0)
assert calls == 3
saved_calls = calls
total = con.execute("SELECT sum(n) FROM nums").fetchone()[0]
assert total == 4950
assert calls == saved_calls
print("callbacks:", calls)
print("next query:", total)
finally:
con.close()用三行结果验证真正的取消
本次输出依次为 cancelled: SQLITE_INTERRUPT、callbacks: 3、next query: 4950。第一行确认数据库操作确实被中断;第二行对应我们设置的停止规则;第三行验证清理以后,连接还能够读取原有数据。最后的计数断言还检查了新查询没有继续调用旧处理器。
这不表示查询准确执行了三百条指令,也不能换算为固定时间。本例先提交样本插入,再中断只读查询,因此后续求和是在清楚的数据状态下进行。若把同样方法用于写入流程,需要另外核对事务状态和重试语义,不能把这次只读结果推广成所有写入都可直接接着做。
调试时可以先保留这里固定的第三次检查,再替换成真实取消标记。这样能分别验证中断通道和界面事件传递;若前者有效而按钮无效,就应追踪标记何时被更新,而不是先改数据库查询。
把检查点与执行时间分开
如果真实需求是响应取消按钮,可以让回调读取专门的取消标记;若要检查截止时刻,则需要自己比较时钟。检查只在回调获得执行机会时发生,耗时的自定义函数等环节可能推迟响应,不能把它宣传成严格的墙钟时间上限。间隔过小还会增加回调开销,应使用代表查询测量。
每条连接同时只有一个进度处理器,新注册会覆盖旧处理器,连接池尤其要明确谁负责安装与移除。SQLite 官方还要求回调不要修改触发它的连接,准备或执行另一条语句也属于这里需要避开的操作。较新 SQLite 还可能在复杂语句准备阶段触发回调,因此记录宜称操作检查次数,而不是已返回记录数。


