pgsql-21 事务与ACID
1. 概述
事务(Transaction)是数据库最核心的概念之一,也是面试必考。事务把一组操作打包成"要么全成功、要么全失败"的原子单元。本篇讲 ACID、隔离级别与并发异常、事务控制;底层的 MVCC 和锁分别见 MVCC多版本并发控制 和 锁机制详解。
2. ACID 特性
| 特性 | 含义 | PostgreSQL 如何保证 |
|---|---|---|
| A 原子性 | 全部成功或全部回滚 | WAL + 事务回滚 |
| C 一致性 | 事务前后满足约束/规则 | 约束 + 触发器 + 应用逻辑 |
| I 隔离性 | 并发事务互不干扰 | MVCC + 锁 |
| D 持久性 | 提交后不丢失 | WAL 落盘(见 WAL 篇) |
-- 经典转账:两步必须同生同死
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 任一步失败则 ROLLBACK,钱不会凭空消失或多出
3. 并发异常(隔离级别要解决的问题)
不加隔离时,并发事务会产生四类异常:
| 异常 | 说明 |
|---|---|
| 脏读 | 读到别的事务未提交的数据(对方可能回滚) |
| 不可重复读 | 同一事务内两次读同一行,值不同(别人改并提交了) |
| 幻读 | 同一事务内两次查同一范围,行数变了(别人插入/删除了) |
| 丢失更新 | 两事务同时改一行,后提交的覆盖了先提交的 |
4. 四种隔离级别
隔离级别越高越安全、并发越低。SQL 标准定义了四级:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 |
| Read Committed | ❌ | 可能 | 可能 |
| Repeatable Read | ❌ | ❌ | 可能(标准)/❌(PG) |
| Serializable | ❌ | ❌ | ❌ |
4.1 PostgreSQL 的实现特点(面试重点)
-- 设置事务隔离级别
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- ...
COMMIT;
-- 查看当前默认
SHOW default_transaction_isolation; -- read committed
三个关键点:
- PG 默认是 Read Committed(每条语句取新快照)。
- PG 没有真正的 Read Uncommitted:即使设置了也等同 Read Committed,永远不会脏读。
- PG 的 Repeatable Read 已消除幻读(基于快照隔离,比 SQL 标准更强);而 SQL 标准的 RR 仍允许幻读。
4.2 对比 MySQL(超高频)
| 维度 | PostgreSQL | MySQL (InnoDB) |
|---|---|---|
| 默认隔离级别 | Read Committed | Repeatable Read |
| RR 下防幻读机制 | MVCC 快照(天然无幻读) | Next-Key Lock(间隙锁) |
| Read Uncommitted | 等同 RC(无脏读) | 真支持(会脏读) |
| Serializable 实现 | SSI 乐观检测冲突 | 加锁(悲观) |
面试金句:
PostgreSQL 默认 Read Committed、MySQL 默认 Repeatable Read,这是两者最常被问到的差异。PG 在 RR 下用 MVCC 快照天然消除幻读,MySQL 则靠间隙锁防幻读——所以 MySQL RR 下范围操作容易产生间隙锁冲突/死锁,而 PG 没有间隙锁概念。
5. Serializable 与序列化失败
PG 的 Serializable 用 SSI(可串行化快照隔离),乐观地检测事务间的读写依赖环,发现可能破坏串行性时让一个事务失败:
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ...
COMMIT; -- 可能报错: could not serialize access due to read/write dependencies
用 Serializable 时,应用必须准备好捕获序列化失败并重试。这是使用该级别的前提。
6. 事务控制与保存点
-- 保存点:部分回滚
BEGIN;
INSERT INTO orders (...) VALUES (...);
SAVEPOINT sp1;
INSERT INTO order_items (...) VALUES (...); -- 假设这步出错
ROLLBACK TO sp1; -- 只回滚到 sp1,orders 保留
INSERT INTO order_items (...) VALUES (...); -- 重试
COMMIT;
-- 只读事务(优化 + 防误写)
BEGIN TRANSACTION READ ONLY;
SELECT ...;
COMMIT;
6.1 自动提交
psql 和多数驱动默认 autocommit:不显式 BEGIN 时每条语句自成一个事务。显式 BEGIN...COMMIT 才把多条打包。
7. 并发控制最佳实践
- 事务尽量短:长事务钉住 MVCC 快照、阻止 VACUUM、占锁(见 VACUUM与表膨胀)。
- 避免 idle in transaction:开了事务不提交危害大。
- 按固定顺序访问资源防死锁。
- 高并发扣减用
SELECT ... FOR UPDATE(悲观)或版本号(乐观)。 - 用 Serializable 时务必实现失败重试。
7.1 乐观锁示例(版本号)
-- 读时记住 version,更新时校验,防丢失更新
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5; -- 影响行数为0说明被别人改过,需重试
8. 面试高频问答
Q1:ACID 分别是什么,PG 怎么保证? 原子性(WAL+回滚)、一致性(约束+触发器)、隔离性(MVCC+锁)、持久性(WAL 落盘)。
Q2:PostgreSQL 和 MySQL 的默认隔离级别分别是什么?(最高频) PG 是 Read Committed,MySQL 是 Repeatable Read。
Q3:PG 的 Repeatable Read 有幻读吗? 没有。PG 的 RR 基于快照隔离,天然消除幻读,比 SQL 标准更强;MySQL 靠间隙锁在 RR 下防幻读。
Q4:四种并发异常是什么? 脏读(读未提交)、不可重复读(两次读同行值变)、幻读(两次读范围行数变)、丢失更新(后者覆盖前者)。
Q5:Serializable 用要注意什么? PG 用 SSI 乐观检测冲突,事务可能因序列化失败被回滚,应用必须捕获错误并重试。
Q6:SAVEPOINT 有什么用?
在事务内设保存点,出错时可 ROLLBACK TO 只回滚部分操作而不放弃整个事务。
9. 下一步
学习 MVCC多版本并发控制,深入理解隔离性的底层实现。
xingliuhua