标准答案

  1. PostgreSQL 的一行数据不是只有一个永久位置。UPDATE 通常会创建新的 tuple 版本,并通过 xmin、xmax 等事务信息记录版本的创建和失效边界。
  2. 查询开始时会依据当前事务隔离级别建立快照,再判断哪些版本对当前事务可见。因此普通 SELECT 通常不会因为另一个事务正在 UPDATE 同一行而直接等待。
  3. 旧版本不能在刚更新后立即删除,因为仍可能有较早快照需要读取它。VACUUM 会清理已经不可见的版本、标记可复用空间,并维护可见性和冻结相关信息。
  4. VACUUM 不等同于 VACUUM FULL。普通 VACUUM 主要在线维护并让后续写入复用空间;VACUUM FULL 会重写表、需要更多临时空间并持有更强的锁。
  5. 线上判断 MVCC 问题要同时看 dead tuples、长事务、autovacuum 进度、表和索引大小、查询延迟及磁盘增长,不能只执行一次清理命令。

题目解析

MVCC 解决的是读写并发时“读哪个版本”的问题,不是让所有写操作都无冲突。两个事务同时修改同一行,仍然可能等待、冲突或由后提交的条件更新失败。

这套机制适合读多写多、希望普通查询不被长写事务直接阻塞的服务,但会把空间回收和事务生命周期变成持续运维事项。长事务即使当前没有执行 SQL,也可能保留旧快照。

如果线上出现表持续变大、autovacuum 追不上或查询读到大量无效版本,应先找长事务和写入热点,再决定是否调 autovacuum、重建索引或安排表重写。

常见误区

  • 误区:认为 UPDATE 会原地覆盖旧行,所以只关注当前行大小。改正:PostgreSQL 通常会产生新 tuple 版本,旧版本回收和索引维护也会影响空间与性能。
  • 误区:认为 MVCC 等于完全无锁。改正:普通快照读与写锁的关系较松,但写写冲突、当前读、DDL 和显式锁仍可能等待。
  • 误区:发现膨胀就把 VACUUM FULL 加进日常定时任务。改正:先判断长事务、autovacuum 和索引膨胀;VACUUM FULL 是需要评估锁、空间和业务窗口的重写操作。

作者信息