atomic 嵌套起来是怎么回事?在里面捕获数据库异常为什么会让后面的查询全部失败?这篇直接看 Django 发给 PostgreSQL 的每一条 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 篇)。
嵌套在 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 省一次保存点,代价是失败时不能局部恢复。