Python 写 SQLite:事务提交和关闭连接是两件事
把几条数据写入 SQLite,看起来只需要执行几次 INSERT。但如果第二条数据违反唯一约束,第一条已经写入的数据该怎么办?把相关写入放进同一个事务,可以让这批操作一起成功,或者一起撤销。另一个容易忽略的问题是:完成事务,并不等于关闭数据库连接。
用一个事务包住一批写入
下面是一个可以单独运行的例子。表名和字段名固定,数据通过占位符传递。示例使用 sqlite3 默认的事务控制方式,没有设置 isolation_level=None 或 autocommit=True。
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
db_path = Path(directory) / "demo.sqlite3"
with closing(sqlite3.connect(db_path)) as conn:
conn.execute(
"CREATE TABLE note (name TEXT UNIQUE NOT NULL)"
)
with conn:
conn.execute("INSERT INTO note VALUES (?)", ("原有记录",))
try:
with conn:
conn.executemany(
"INSERT INTO note VALUES (?)",
[("新记录",), ("原有记录",)],
)
except sqlite3.IntegrityError:
print("本批写入已回滚")
rows = conn.execute("SELECT name FROM note").fetchall()
print(rows) # [('原有记录',)]
第一次 with conn 正常结束,原有记录被提交。第二次批量插入中,“新记录”先成功执行,随后“原有记录”与唯一约束冲突。异常离开 with conn 时,当前事务回滚,因此数据库里只剩第一次已经提交的记录。
这里的 CREATE TABLE 是初始化步骤。不要据此推断建表、迁移和任意 SQL 都会自动进入同一个事务。Python 的事务控制配置会影响隐式 BEGIN 的时机;复杂迁移应明确设计事务边界,并针对所用版本验证。
异常要离开事务代码块
连接的上下文管理器会在代码块正常结束时提交已有事务,在异常退出时回滚。若在 with conn 内部捕获错误并直接忽略,代码块可能正常结束,前面已经执行的写入就可能被提交。因此,示例把 except 放在事务代码块外面。
如果业务确实允许部分成功,需要明确定义哪些记录可以保留,必要时使用保存点,而不是无意中吞掉异常。也应避免在这批操作中途手动调用 commit(),否则后面的回滚无法撤销已提交的数据。
提交之后仍要关闭连接
with conn 管理事务,退出时不会自动关闭连接。外层 contextlib.closing 才负责在结束时调用 close(),包括出现异常的情况。两层代码块表达了不同职责:内层决定提交或回滚,外层释放连接和相关资源。
关闭连接也不能代替提交。对于仍有未提交写入的连接,不要期待 close() 自动帮你保存。把成功路径的提交和资源释放分别写清楚,程序的行为才容易检查。
占位符解决的是数据值传递。它不能直接替代表名或列名;需要动态选择字段时,应在调用方维护允许列表,避免拼接不受控制的标识符。
最后,事务并不能让所有并发写入同时成功。多个连接竞争写锁时仍可能遇到锁等待或超时,耗时的网络请求不适合放在写事务里。本地验证可以先覆盖正常提交、唯一约束失败后整批回滚,以及异常后连接释放三个场景,再接入真实业务。
本文由 AI 辅助撰写;示例已在本地验证正常提交、批量插入失败后的回滚,以及连接关闭后拒绝继续执行 SQL 的行为。
- 点赞
- 收藏
- 关注作者
评论(0)