bigywater.tech
Django · 44

transaction.atomic() 与保存点

atomic 嵌套起来是怎么回事?在里面捕获数据库异常为什么会让后面的查询全部失败?这篇直接看 Django 发给 PostgreSQL 的每一条 SQL。

SAVEPOINT
内层 atomic 用保存点实现:异常时 ROLLBACK TO SAVEPOINT,外层不受影响
TransactionManagementError
atomic 里捕获了 IntegrityError 却没有内层 atomic,之后的查询全部失败
savepoint=False
内层失败会让整个外层事务作废

嵌套 atomic 的 SQL

实测 · 本机执行输出
    BEGIN
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('a', 0) RETURNING "lab_acct"."id"
    SAVEPOINT "s8399224704_x1"
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('b', 0) RETURNING "lab_acct"."id"
    RELEASE SAVEPOINT "s8399224704_x1"
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('c', 0) RETURNING "lab_acct"."id"
    COMMIT

只有最外层才会 BEGIN / COMMIT;里面的每一层只是保存点。

内层失败

实测 · 本机执行输出
    BEGIN
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('a', 0) RETURNING "lab_acct"."id"
    SAVEPOINT "s8399224704_x1"
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('b', 0) RETURNING "lab_acct"."id"
    ROLLBACK TO SAVEPOINT "s8399224704_x1"
    RELEASE SAVEPOINT "s8399224704_x1"
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('c', 0) RETURNING "lab_acct"."id"
    COMMIT
  最终表里: ['a', 'c']

捕获异常的位置很重要:让异常在 with atomic() 的内层「穿过」出口,Django 才会执行 ROLLBACK TO SAVEPOINT。

最常见的坑

实测 · 本机执行输出
  捕获到 IntegrityError: duplicate key value violates unique constraint "lab_acct_name_key"
  之后的查询 → TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
  ——改成内层再包一层 atomic:
  捕获到 IntegrityError(只回滚到保存点)
  最终表里: ['after', 'dup']

PostgreSQL 里一条语句出错,整个事务进入「已中止」状态,直到回滚。所以要「尝试插入、失败就换个办法」,必须把尝试包在内层 atomic(保存点)里。get_or_create 内部就是这么做的(第 35 篇)。

durable、savepoint=False、autocommit

实测 · 本机执行输出
  嵌套在 atomic 里的 durable=True → RuntimeError: A durable atomic block cannot be nested within another atomic block.
    BEGIN
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('x', 0) RETURNING "lab_acct"."id"
    ROLLBACK
  savepoint=False 的内层失败之后,外层被标记 needs_rollback,外层提交时: TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
  表里: []
  没有 atomic 的单条 create:
    INSERT INTO "lab_acct" ("name", "bal") VALUES ('auto', 0) RETURNING "lab_acct"."id"

durable=True 用来断言「这里必须是最外层事务」(比如需要在其后可靠执行 on_commit);savepoint=False 省一次保存点,代价是失败时不能局部恢复。

分步演示:事务与保存点

atomic 与保存点
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。PostgreSQL 16.15(本机临时实例,端口 9420);SQL 用 CaptureQueriesContext 记录
    • 没有验证:SQLite / MySQL 上保存点的行为差异、autocommit=False 的用法、数据库事务超时、异步代码里的 atomic(第 119 篇)
    京ICP备2026067502号-1京公网安备11010502063487号