PostgreSQL MVCC 与快照可见性
理解行版本、语句快照、长事务和 vacuum 之间的关系
MVCC(多版本并发控制)让普通读取通常不阻塞写入。UPDATE 不会就地覆盖所有读者看到的值,而会产生新行版本;每个查询按自己的 snapshot 判断哪个版本可见。
两个会话观察快照
先准备数据:
CREATE TABLE mvcc_demo (
id integer PRIMARY KEY,
value text NOT NULL
);
INSERT INTO mvcc_demo VALUES (1, 'before');会话 A:
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT value FROM mvcc_demo WHERE id = 1; -- before会话 B:
UPDATE mvcc_demo SET value = 'after' WHERE id = 1;
COMMIT;回到会话 A:
SELECT value FROM mvcc_demo WHERE id = 1; -- 仍是 before
COMMIT;
SELECT value FROM mvcc_demo WHERE id = 1; -- afterREAD COMMITTED 则在每条语句开始时取得新 snapshot,所以同一事务里的第二次查询可能看到会话 B 已提交的值。
为什么长事务危险
只要旧 snapshot 仍可能看到某些行版本,vacuum 就不能把它们当作完全可回收。长事务因此会扩大:
- dead tuples 与表/索引膨胀;
- vacuum 工作量和磁盘占用;
- 复制槽、逻辑解码或 standby 的保留压力;
- transaction ID wraparound 风险窗口。
查找持有旧事务或 snapshot 的会话:
SELECT
pid, usename, application_name, state,
now() - xact_start AS transaction_age,
age(backend_xmin) AS snapshot_xid_age,
wait_event_type, wait_event,
left(query, 120) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL OR backend_xmin IS NOT NULL
ORDER BY xact_start NULLS LAST;不要仅凭“时间长”终止会话;先确认业务、是否在执行备份/维护、事务能否安全重试及终止影响。
MVCC 不等于没有锁
行版本解决读取可见性,写写冲突、DDL、外键检查和显式锁仍会等待。下一步阅读锁等待与死锁。
权威行为见 PostgreSQL 18 并发控制和事务隔离。
Last updated on