目录

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

三个关键点:

  1. PG 默认是 Read Committed(每条语句取新快照)。
  2. PG 没有真正的 Read Uncommitted:即使设置了也等同 Read Committed,永远不会脏读。
  3. 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. 并发控制最佳实践

  1. 事务尽量短:长事务钉住 MVCC 快照、阻止 VACUUM、占锁(见 VACUUM与表膨胀)。
  2. 避免 idle in transaction:开了事务不提交危害大。
  3. 按固定顺序访问资源防死锁。
  4. 高并发扣减用 SELECT ... FOR UPDATE(悲观)或版本号(乐观)。
  5. 用 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多版本并发控制,深入理解隔离性的底层实现。