# BEGIN、COMMIT、ROLLBACK

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

---

LLMS 索引： [llms.txt](/llms.txt)

---

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

## 本章要点 {#learn}

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

## 示例 {#example}

```sql {num="46.1" caption="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`。要在事务中途优雅地处理错误，请使用保存点（下一课）。

> [!IMPORTANT] 预期错误

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

## 运行示例 {#run}

运行方式：

```text
$ 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)
 ...
```

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

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

> [!NOTE] 动手练习
> 清理过程中脚本会删除 `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` 将看到新值。

## 下载 {#downloads}

SQL 文件：[begin-commit-rollback.sql](/examples/source/begin-commit-rollback.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
