【问题标题】:PostgreSQL - MVCC (multi-version concurrency control) - When is actual lock acquired?PostgreSQL - MVCC(多版本并发控制) - 何时获取实际锁?
【发布时间】:2026-01-25 17:40:01
【问题描述】:

据我了解,postgres 使用两个额外的字段 Xmin 和 Xmax 来实现 mvcc, 假设我们有 Employee 表,其中包含 id 和 name 列。

以下是一些 crud 操作以及它们如何同时工作(考虑隔离级别 = READ_COMMITTED),问题是何时何地获取实际锁。

  1. 插入 -> 新事务插入一条新记录,在提交之前对其他事务不可见,因此在这种情况下没有问题,也不需要锁定或版本控制。假设 id = 1,name = "aa" 被插入。 Postgres 为 mvcc Xmin = current txn id(假设为 100)和 Xmax = 0/null 添加了 2 个额外的列。
id  |   name | Xmin | Xmax
------------------------------
1   |   aa   | 100  | null
  1. 使用并发读取更新 -

    一)。一个新事务开始将名称更新为“bb”(对于 id = 1)。同时还有一个 事务开始读取相同的数据。

    b)。使用 Xmin = 当前事务 id(假设为 200)和 Xmax = null 以及 id = 1、name = bb 创建一个新的元组(postgres 中的不可变对象表示一行)。旧版本的 id = 1 也被更新为 Xmax = 200。读取事务看到旧版本的数据 Xmin = 100 并返回。 在这种情况下是否需要任何锁定?我认为没有,但它可能会更新旧元组的 Xmax。

以下是多个版本的相同记录(仅用于说明目的),最新版本的 Xmax = null。

id  |   name | Xmin | Xmax
------------------------------
1   |   aa   | 100  | 200
1   |   bb   | 200  | null
  1. 同时更新更新 -

    一)。交易(txn id = 300)开始将 id = 1 更新为 name = cc。另一个事务(txn id = 400)开始将同一记录(id = 1)更新为name = dd。如果这种情况也通过创建新元组并标记旧元组的 Xmax 以同样的方式进行,那么我认为它会产生问题,因为 300 和 400 都会创建一个新元组并标记旧元组的 Xmax = txn id。在这种情况下,更新可能会丢失。

在这种情况下,排他锁是由第一个 txn 获取,其他并发更新 txns 会等到任何正在进行的 txn 完成或 postgres 有其他处理方式吗?

【问题讨论】:

    标签: database postgresql database-design database-concurrency mvcc


    【解决方案1】:

    插入 -> 新事务插入一条新记录,该记录在提交之前对其他事务不可见,因此在这种情况下没有问题,也不需要锁定或版本控制。

    这不是真的。插入的元组在插入时被锁定。例如,如果存在唯一约束并且其他人尝试插入冲突的元组,这很重要。

    使用并发读取更新......在这种情况下是否需要任何锁定

    当 xmax 被更新时,在保存元组的缓冲区上有一个“轻量级”锁,该锁将在字段更新后立即释放(在事务期间不保持)。这种轻量级锁包含一个屏障,以确保任何其他进程都能看到所做的更改,而不是看到它的过时缓存版本。

    读者会看到,取决于它何时到达那里,要么 xmax 为 0 并返回元组,要么它会看到 xmax 为 200 并看到 200 尚未提交,并返回元组,因为它知道 xmax = 200 表示的锁不适用于它,只是一个读者。

    同时更新更新...

    第一个将其 id 写入将要废弃的元组的 xmax 的进程将获胜。第二个将在 xmax 中看到其他人的有效 id,并阻塞直到其他事务提交或回滚,然后决定要做什么。由于保存元组的缓冲区上的轻量级锁,它们不能同时更新 xmax 而不会注意到彼此的变化。

    【讨论】:

      最近更新 更多