科技 · 实用指南
SQLite RETURNING 取过一行仍无法提交?结束每个写入游标
拿到 ID 不一定代表语句执行完毕。重放写入或丢弃结果前,先比较单行、多行和两个游标的行为。

INSERT ... RETURNING 后 COMMIT 报“SQL statements in progress”时,先检查自己的未完成写入结果,别直接认定是其他进程锁住数据库。收到一个 ID 与语句执行完毕是两件事。
八组临时文件测试说明 fetchone 何时有效、为何不能推广,以及关闭游标为什么能让写入提交,却丢失应用所需的返回 ID。
1. 记录驱动与事务控制方
记录 Python、sqlite3.sqlite_version、完整 SQL、所有仍存活的结果游标和事务设置。测试使用 Linux 上的 CPython3.12.14 与 SQLite3.53.1,明确设置 autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL 和 isolation_level="DEFERRED"。结果仅针对标准库 sqlite3,不代表 APSW 或 ORM。
保留完整异常和失败阶段。SQLite 允许有未完成 SELECT 时执行显式 COMMIT,但未完成写入可能阻止提交。检查本连接的 RETURNING 写入前,不要把所有 SQLITE_BUSY 都归为外部争用或先加大超时。也不要通过切换事务模式意外提交。
2. 单行成功不是通用规则
每组测试都创建新的文件和 t(x integer) 表。未读取的 INSERT INTO t VALUES(1) RETURNING x 阻止提交;在这个驱动中,用 fetchone() 取出唯一一行后可提交。但 INSERT INTO t VALUES(1),(2),(3) RETURNING x 只取一行后,提交仍然失败。
另两组三行样本分别用 fetchall()、反复 fetchmany(1) 直至空批次,均可提交,重新打开后保留全部三个值。继续前应保存每个所需 ID。不能假定返回顺序来配对输入:SQLite 不保证 RETURNING 顺序。分批读取限制的是 Python 列表大小,不能消除引擎已缓冲的 RETURNING 输出。
3. 结束所有写入游标,保留所需输出
第六组关闭未读取的三行游标后,提交成功,重新打开可见三个插入值,但应用没有收集到任何返回行。因此 Cursor.close() 不能代替回滚,也不能代替交付所需 ID。只有明确决定放弃这些输出时,才采用这种处理。
另外两组在同一连接保留两个 INSERT ... RETURNING 游标。读取完第一个后,第二个仍未完成,提交失败;两个都读完后提交成功,重新打开可见值 1 和 99。由事务控制组件保留游标引用,不要让辅助函数返回一个可见结果,却悄悄留下另一个写入结果。
4. 返回结果与提交分开验收
所有提交失败的测试在尝试后,in_transaction 仍为 true。测试随后关闭未完成游标并明确回滚,重新打开后未见插入行。空结果来自主动清理,不是提交异常自动撤销了写入。事务仍活跃时重放 INSERT,可能执行额外写入。
需要返回结果时,应先读取并验证相关输出、关闭游标,再由约定的事务控制方提交。从新连接核对持久化值,并保留准确行数的回归测试。出错时遵循应用回滚策略,不要因为拿到 ID 就报告成功。本次没有测试并发读者、触发器、大量输出或其他 autocommit 模式,应分别诊断这些情形。
