SQLite 的 with 块为何没有撤回写入:executescript 的提交边界

举报
yd_294604443 发表于 2026/11/05 22:50:15 2026/11/05
【摘要】 把多次写入放进同一个 with 块,不一定能保证异常时全部撤回。本文用 Python 标准库的四组本地合成实验,说明 sqlite3 的兼容事务模式与 executescript 如何改变提交边界,并给出一个可运行的批量写入例子。

批量导入时,常见需求是“全部成功才保存”。如果两次写入之间换用了执行脚本的接口,即使缩进没有改变,事务也可能已经结束。排查这类问题,需要看实际提交发生在哪里。

同一段缩进,不代表同一个事务

本次使用 Python 3.12.14、SQLite 3.53.1,为每组实验新建一个数据库,先建表并提交,再进入被测批次。前三组显式设置 autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL,固定该版本的默认兼容模式,避免把结论依赖于未来默认值。

每组都先在 with con: 内插入标签 before,再执行第二次写入,最后主动抛出异常。异常穿出 with 块后才被捕获。关闭写入连接,再用新只读连接查询,结果如下:

第二次操作 事务模式 异常后留下的标签
execute 插入 LEGACY 无
executescript 插入 LEGACY before、in_script
脚本先 BEGIN 再插入 LEGACY before
executescript 插入 autocommit=False 无

连接的上下文管理器会在退出时处理待提交事务;进入 with 本身不会自动开启事务,也不会把此前或期间已经提交的修改重新纳入回滚。

给脚本加 BEGIN,为什么还留下一条

第二组 SQL 跟踪依次出现 BEGIN、第一次 INSERT、COMMIT、脚本内 INSERT。兼容模式下,executescript() 发现已有事务,会先提交它,然后执行脚本;它没有自动把整段脚本包装成一个新事务。本例脚本只有一条 INSERT,执行完就已保存。

第三组把脚本改为 BEGIN; INSERT ...;,异常后脚本内写入被回滚,但 before 仍在。原因是脚本里的 BEGIN 发生得太晚:接口先提交了前一段,新的事务只能覆盖后面的语句。不能靠在脚本文本开头补一个 BEGIN,追回此前的提交。

第四组改用 autocommit=False,同样不含事务控制语句的脚本参与当前事务,两条都被回滚。这个参数从 Python 3.12 起提供,应在代码中明确选择模式;也不能由本例推断任意脚本都能整批撤回,脚本自己包含 COMMIT 时还会改变边界。

批量数据写入,保持接口与异常边界清楚

下面的独立示例采用参数化 executemany(),用重复值触发唯一约束。它只针对重复执行同一条 INSERT 的批量写入,不执行任意 SQL 脚本,也不是 executescript() 的通用替代:

import sqlite3
from contextlib import closing

with closing(sqlite3.connect(":memory:", autocommit=False)) as con:
    con.execute("CREATE TABLE task(name TEXT UNIQUE)")
    con.commit()
    try:
        with con:
            con.executemany("INSERT INTO task VALUES (?)",
                            [("A",), ("A",)])
    except sqlite3.IntegrityError:
        print("拒绝重复,整批回滚")
    print(con.execute("SELECT count(*) FROM task").fetchone()[0])

本次输出为“拒绝重复,整批回滚”和 0。捕获异常放在事务 with 外面,让管理器先看到失败并回滚;若在块内吞掉异常再正常退出,前面成功的写入可能被提交。参数占位符用于传值,不能拿它替换表名,也不应把外部数据拼进 SQL 脚本。

外层 closing() 负责关闭连接,因为 with con: 本身不负责关闭。这个连接专用于示例批次,建表后已经提交;复用连接时,要先确认是否存在其他待处理写入,避免一并提交或回滚。

本地观察的范围

2026年11月5日,四组观察与文中代码均实际执行,两个子进程退出0,stderr为空,原始输出、退出码、SQL 跟踪和文件指纹已保存。autocommit=False 在回滚后会重新开启事务,因此本次 in_transaction 仍为真;它不表示被回滚的数据还存在。

全部数据库和内容均为有限合成输入,实验没有接入云服务,也未测并发、锁等待、断电恢复或生产导入。验证支持的是这些模式和语句下的提交结果;完整导入还需要另外约定字段校验、重复处理和失败重试。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。