跳转到主要内容

46 BEGIN、COMMIT、ROLLBACK

将变更分组到事务中,提交或回滚,并观察一次预期的失败。

事务将多条语句组合成一个全有或全无的单元。BEGIN(或 START TRANSACTION)启动事务;COMMIT 使变更永久生效;ROLLBACK 丢弃变更。在事务之外,每条语句都会在各自的隐式事务中运行——PostgreSQL 始终在事务中运行,你只需选择是否显式标记事务边界。事务让你能在多步变更中保持数据一致。

本章要点

  • 使用 BEGIN 开始事务,并以 COMMIT 或 ROLLBACK 结束
  • ACID 属性(尤其是原子性和隔离性)
  • 每条语句的隐式事务
  • 隔离级别:READ COMMITTED(默认)、REPEATABLE READ、SERIALIZABLE
  • ABORT / “事务已中止”状态

示例

示例 46.1 BEGIN、COMMIT 与 ROLLBACK
-- A transaction that we throw away
BEGIN;
DROP TABLE IF EXISTS tx_demo;
CREATE TABLE tx_demo (id integer PRIMARY KEY, val text);
INSERT INTO tx_demo VALUES (1, 'a');
ROLLBACK;

-- The table does not exist after rollback
SELECT count(*) FROM pg_tables WHERE tablename = 'tx_demo';

-- A transaction that we keep
BEGIN;
CREATE TABLE tx_demo (id integer PRIMARY KEY, val text);
INSERT INTO tx_demo VALUES (1, 'a'), (2, 'b');
COMMIT;

SELECT * FROM tx_demo ORDER BY id;

-- Set isolation level for the next transaction
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT count(*) FROM tx_demo;       -- sees a consistent snapshot
COMMIT;

-- When a statement errors inside a transaction, the whole transaction is aborted.
-- Subsequent statements fail until you ROLLBACK.
BEGIN;
INSERT INTO tx_demo VALUES (1, 'duplicate');  -- PK violation
-- Any further statements here would fail with "current transaction is aborted".
ROLLBACK;

-- Cleanup
DROP TABLE tx_demo;

PostgreSQL 提供完整的 ACID 保证:

  • 原子性:事务中的所有变更要么全部生效,要么一项也不生效。
  • 一致性:数据库会检查约束,并从一个有效状态转移到另一个有效状态。
  • 隔离性:并发事务看不到彼此尚未提交的变更(严格程度因隔离级别而异)。
  • 持久性:在正常持久性设置下,成功的 COMMIT 会让已写入日志的变更在崩溃后仍然存在;synchronous_commit = off 等设置会改变这一保证。

默认隔离级别是 READ COMMITTED:每条语句看到的是它开始执行时的数据库快照。REPEATABLE READ 为整个事务提供一个快照——重复读取会看到相同的数据。SERIALIZABLE 会增加对串行化异常的检查,并可能回滚那些会产生非串行调度的事务。

PostgreSQL 的事务错误模型很严格:事务内的任何错误都会中止该事务。后续语句会失败,并显示*“current transaction is aborted, commands ignored until end of transaction block”*,直到执行 ROLLBACK。要在事务中途优雅地处理错误,请使用保存点(下一课)。

预期错误

本课特意插入一条会造成主键重复的记录,以演示事务失败。使用 psql 默认的 ON_ERROR_STOP=off 运行它,这样后续的 ROLLBACK 和清理操作才能执行。其他课程可以使用 -v ON_ERROR_STOP=1。

运行示例

运行方式:

$ psql -f source/begin-commit-rollback.sql postgres
BEGIN
DROP TABLE
CREATE TABLE
INSERT 0 1
ROLLBACK
 count
-------
     0
(1 row)
BEGIN
CREATE TABLE
INSERT 0 2
COMMIT
 id | val
----+-----
  1 | a
  2 | b
(2 rows)
 ...

常见陷阱

  • 在交互式 psql 中忘记 COMMIT,意味着断开连接时所做的一切都会被回滚。如果希望显式控制,请设置 \set AUTOCOMMIT off;默认的 AUTOCOMMIT on 会让每条语句在各自的隐式事务中运行。
  • 长时间运行的事务会持有行锁,并阻止自动清理回收死元组。请保持事务短小。
  • 在 PostgreSQL 中,把模式变更(DDL)和数据变更(DML)放在同一事务中没有问题——不同于 MySQL,DDL 也是事务性的。
  • 在 READ COMMITTED 下,同一事务中的两条语句可能看到同一行的不同值(如果并发事务在两条语句之间提交)。若要进行一致的多语句读取,请切换到 REPEATABLE READ。
提示

BEGIN; ... ROLLBACK; 可以在演练期间撤销事务性表变更,但它仍会获取锁并运行触发器。序列递增和外部副作用不会回滚。请使用临时数据库进行这些练习。

动手练习

清理过程中脚本会删除 tx_demo。先使用 CREATE TABLE tx_demo (id integer PRIMARY KEY, val text); INSERT INTO tx_demo VALUES (1, 'a'); 重新创建它。然后打开两个 psql 会话。在会话 A 中:BEGIN; UPDATE tx_demo SET val = 'x' WHERE id = 1;(先不要提交)。在会话 B 中:SELECT * FROM tx_demo;——你应该看到旧值。然后在会话 A 中执行 COMMIT;,会话 B 的下一次 SELECT 将看到新值。

下载

SQL 文件:begin-commit-rollback.sql · 全部示例文件 · 准备工作