Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python sqlite3 报“cannot start a transaction within a transaction”?先找出事务所有者

这不是锁等待超时,而是同一连接已有活动事务。记录 Python 模式、保住原子边界,并只用一个外层事务或 SAVEPOINT。

Original diagram mapping Python transaction policy, live connection evidence, and the intended outer transaction owner.
原创示意图:先记录策略与状态,再选择事务所有者。 Watch at Ease

SQLite 不允许在活动事务中再执行 BEGIN。Python 中真正难找的是第一个事务由谁打开:autocommit=False 会维持事务,旧 isolation_level 会在 DML 前隐式开始,外层 helper 可能已拥有事务,脚本也可能自带 BEGIN。

不要靠到处插入 commit 消掉异常。先在失败行前记录 autocommit、isolation_level 和 in_transaction,再明确工作单元所有者并选定一种模型。

冻结失败 BEGIN 处的证据

记录失败 SQL、autocommit、isolation_level、in_transaction,以及同连接最近的 DML、BEGIN、SAVEPOINT、executescript、commit 和 rollback。

不要从调用栈距离推断所有权;外层上下文或连接策略可能早已开启事务。

只选一个外层事务所有者

应用拥有模式由调用者开关工作单元,底层 helper 不执行 BEGIN 或 COMMIT;驱动拥有模式则使用连接策略和 commit/rollback。

脚本若自带 BEGIN/COMMIT,不要再包一层;用故意的中途失败验证回滚。

有意嵌套时使用 SAVEPOINT

内层需要可回滚作用域时,用唯一名称 SAVEPOINT,成功 RELEASE,失败 ROLLBACK TO 后再 RELEASE。

可复用库通常不应提交它不拥有的外层事务。

把 BEGIN IMMEDIATE 当锁时机选择

BEGIN IMMEDIATE 只把写锁获取提前,仍不能在同连接活动事务内执行。

若旧模式 DML 已开 DEFERRED,要判断此前工作是否同属一个单元,再决定保留、提交或重构边界。

证明提交与回滚边界

构造首个写入后故意失败的测试,确认整个预期单元都回滚;再覆盖成功和嵌套 helper。

在 API 边界断言 in_transaction,让模式或默认值变化尽早暴露。

Original diagram comparing a nested BEGIN failure with one outer transaction and an inner SAVEPOINT.
原创示意图:SQLite 的内层可回滚作用域使用 SAVEPOINT,而不是第二个 BEGIN。 Watch at Ease