PostgreSQLNotes

第 14 章:事务与隔离级别

zjc 于 2026-01-14 发布

这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 事务是数据库保证一致性的基本单位。PostgreSQL 使用 MVCC 实现读写互不阻塞,并通过锁和谓词机制处理写冲突。

14.1 基本用法

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;

回滚:

ROLLBACK;

保存点:

BEGIN;
INSERT INTO orders(order_no) VALUES ('O001');
SAVEPOINT sp1;
UPDATE orders SET status = 'PAID' WHERE order_no = 'O001';
ROLLBACK TO SAVEPOINT sp1;
COMMIT;

14.2 ACID

特性 PostgreSQL 实现
Atomicity 事务提交或回滚
Consistency 约束、触发器、应用规则
Isolation MVCC + 锁
Durability WAL 与同步提交策略

14.3 隔离级别

BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

PostgreSQL 默认 READ COMMITTED

级别 防止
Read Committed 脏读
Repeatable Read 脏读、不可重复读
Serializable 脏读、不可重复读、幻读、写倾斜

PostgreSQL 的实现细节与 SQL 标准描述并不一一对应,应通过行为验证。

14.4 异常现象

脏读

读到未提交数据。PostgreSQL 隔离级别下均不允许。

不可重复读

同一事务两次读取同一行结果不同。

BEGIN;
SELECT amount FROM orders WHERE id = 1;
-- 其他事务修改并提交
SELECT amount FROM orders WHERE id = 1;
COMMIT;

幻读

同一查询条件两次返回不同行集。

写倾斜

两个事务分别读取相交条件,再分别修改不同行,导致业务约束被破坏。SERIALIZABLE 通过 SSI 防止。

14.5 锁冲突

SELECT FOR UPDATE;
SELECT FOR SHARE;
SELECT FOR NO KEY UPDATE;
SELECT FOR KEY SHARE;

常见冲突:

操作 冲突
UPDATE 同一行 行锁等待
外键检查 引用行锁
唯一约束 唯一索引等待
DDL AccessExclusiveLock

14.6 SERIALIZABLE

BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT count(*) FROM seats WHERE room_id = 1;
INSERT INTO seats(room_id, user_id) VALUES (1, 1001);
COMMIT;

可能返回:

ERROR: could not serialize access due to read/write dependencies among transactions

应用必须重试。串行化隔离会增加谓词锁和取消概率,适合强一致性关键业务。

14.7 事务边界

推荐:

  1. 事务尽量短;
  2. 不在事务中调用慢外部服务;
  3. 批量任务分批提交;
  4. 重试逻辑处理序列化失败和死锁;
  5. 幂等键防止重试重复;
  6. 避免交互式客户端长期打开事务。

14.8 查看事务

SELECT
    pid,
    state,
    xact_start,
    now() - xact_start AS duration,
    query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY xact_start;

长事务会阻止 VACUUM 回收旧版本,是 PostgreSQL 高发问题。

本章小结

PostgreSQL 通过 MVCC 和锁提供多级隔离。默认 Read Committed 适合多数业务;需要快照一致或串行化时,要接受相应代价并设计重试。事务边界直接影响并发和清理能力。

思考题

  1. 默认隔离级别下能否出现不可重复读?
  2. SERIALIZABLE 为什么要重试?
  3. 长事务有哪些危害?
  4. SELECT FOR UPDATE 适合什么场景?
  5. 事务中为什么不能调用慢外部服务?