整体理解非常到位!以下保留原始说话逻辑,修正小口误,去掉卡顿,标出原文几处小漏洞,直接照着说即可。

原文小勘误

  1. MVCC 全称:多版本并发控制,不是多并发版本控制(口误颠倒)
  2. 更新出来新 tuplexmax=00 不是代表事务活跃,是哨兵——没有被删除/修改过
  3. 可见性判断:不是只满足两个条件,要组合判断;xmin 必须提交合格之后,才去看 xmax

面试口述版(完整)

PostgreSQL 的 MVCC 叫多版本并发控制,目的是实现读写不互相阻塞。

PG 里面一行逻辑数据,物理上叫 tuple 元组。每个 tuple 会自带两个隐藏列:xminxmax

  • xmin:生成这个版本的事务 ID,插入或者更新产生新版本的时候赋值。
  • xmax:让这个版本过期、删除的事务 ID;xmax=0 是特殊标记,代表这个版本还没有被任何人删除或者更新。

PG 修改数据不会原地覆盖,是追加新版本。

当我更新一行的时候,不会改动原来的旧元组:

  1. 旧 tuple,把它的 xmax 设置为当前更新事务的 ID,旧版本就被标记待过期,但不会立刻删掉,仍然留在堆表里。
  2. 再生成一条全新的 tuple,新版本的 xmin 就是本次更新的事务 ID,xmax 赋值 0。

这样堆表里就同时存在多个物理 tuple,代表同一行逻辑数据,历史版本全部留在堆中。

每个事务开启的时候会拿到一份快照,快照记录当下所有正在活跃、还没提交的事务 ID。读数据的时候,扫描每一个物理 tuple,结合快照做可见性判断:

首先看 xmin:这个版本是谁创建的。如果 xmin 对应的事务还活跃没提交,这个版本直接不可见;只有 xmin 事务已经提交,才有资格继续判断。

再看 xmax

  • 如果 xmax=0:没有事务来删除它,那就可见。
  • 如果 xmax 是一个真实事务 ID:
    • xmax 对应的事务还活跃没提交:代表删除/更新动作还没有生效,这个 tuple 依旧可见。
    • xmax 对应的事务已经提交:代表这个版本正式作废,不可见。

这些死掉、不再被任何事务看见的死元组,不会自动回收,需要 Vacuum 来清理。如果有长时间运行的老事务,Vacuum 就不能清理死元组,就会出现表膨胀


极简版(1 分钟,适合时间很短的面试)

PG 的 MVCC 是多版本并发控制,读写互不阻塞。

每一个物理元组 tuple 有隐藏的 xminxmax。更新不原地修改,而是追加新版本。旧元组填上 xmax 标记过期,新旧版本全部留在堆表。

事务拥有快照,记录当前活跃事务。判断可见性:先看 xmin,生成版本的事务必须提交;再看 xmax,如果干掉它的事务已经提交,版本就作废。

废弃的死元组靠 Vacuum 清理,长事务会阻止 Vacuum,造成表膨胀。PG 没有 undo log,历史版本存在堆表里,这是和 InnoDB 很大的区别。