PostgreSQL/MVCC
在几乎所有情况下,PostgreSQL 数据库必须支持许多客户端,这些客户端希望同时添加或更改数据。 这使得有必要保护并发运行的请求彼此不受影响 - 最好是不用阻塞它们。 可能会出现两种情况:两个客户端想要同时更改同一行,或者一个客户端想要撤销(回滚)他的更改,而另一个客户端可能已经尝试读取最新版本。
想象一下,一个在线商店提供最后一款商品。 两个客户端在他们的用户界面上显示该商品。 一段时间后,但同时,两个客户端都决定将该商品放入他们的购物车,甚至购买它。 两个人都看到了这件商品,但只允许其中一个人购买。 数据库必须强制执行请求的顺序,允许其中一个写入访问,阻止另一个写入,并通知被阻止的客户端数据已由其他进程更改,应重新读取。
PostgreSQL 实现了一种复杂的技术来处理并发访问,避免锁定:多版本并发控制 (MVCC)。 与锁定一行不同,MVCC 技术在数据更改发生时创建该行的最新版本。 “使用 MVCC... 而不是锁定的主要优势在于,在 MVCC 中,用于查询(读取)数据的锁不会与用于写入数据的锁冲突,因此读取永远不会阻塞写入,而写入永远不会阻塞读取。 PostgreSQL 即使在通过使用... 可序列化快照隔离 (SSI) 提供最严格的交易隔离级别时,也保持此保证”。 [1]
MVCC 的实现基于事务 ID (XID)。 集群中的每个事务都会获得一个唯一的顺序编号作为其 ID。 每个 INSERT
、UPDATE
或 DELETE
命令都会将 XID 存储在受影响行的 xmin
或 xmax
中。 xmin
、xmax
和一些其他系统列包含在每行中。 这两个列通常使用 SELECT * FROM ...
命令不可见。 但可以使用 SELECT xmin, xmax, * FROM ...
之类的命令读取它们。 xmin
列包含创建此行版本的交易的 XID,xmax
列包含删除此版本的交易的 XID,或者如果该版本未被删除,则为零。
那么,当写入访问发生时,具体发生了什么呢? 下面的图形显示了关于 xmin
、xmax
和常规应用程序数据的详细信息。
INSERT
命令创建行的第一个版本。 除了其应用程序数据“x”之外,此版本还包含创建交易 123 的 ID 在 xmin
中,而 0 在 xmax
中。 xmin
表示该版本自交易 123 以来存在,而 xmax
中的值 0 表示它当前未被删除。
稍后,交易 135 通过将应用程序数据从“x”更改为“y”来执行此行的 UPDATE
。 根据 MVCC 原则,行旧版本的 data 不会更改。 值“x”保持原样。 只有 xmax
会更改为 135。 现在,此版本被视为仅对 XID 从 123 到 134 的交易有效。 除了保留旧版本的 data 外,UPDATE
还使用其 XID 在 xmin
中、0 在 xmax
中和“y”在应用程序 data 中 (以及旧版本中的所有其他应用程序 data) 创建完整行的最新版本。 此新行版本对所有将来的交易可见。 (在内部,UPDATE
命令充当 DELETE
命令,后跟 INSERT
命令。)
所有后续的 UPDATE
命令的行为与第一个命令相同:它们将其 XID 放入当前版本的 xmax
中,并使用其 XID 在 xmin
中和 0 在 xmax
中创建最新版本。
最后,DELETE
命令可能会删除一行。 即使在这种情况下,包括最新版本在内的行的所有版本都保留在数据库中 - 不会丢弃任何东西。 只有最后一个版本的 xmax
设置为 DELETE
交易的 XID,这表示它仅对 XID 较早的交易可见 - 在此示例中,从 142 到 820。
总之,MVCC 技术在表的堆文件中创建了越来越多相同行的版本,并将它们保留在那里,即使在 DELETE
命令之后也是如此。 只有最年轻的版本对所有未来的交易相关。 但该系统还必须在短时间内保留一些旧版本,因为它们可能仍然被早于删除交易并因此具有较小 XID 的交易请求。 随着时间的推移,即使是最旧的版本也对所有交易变得无关紧要,因此最终变得不再需要。 然而,它们确实物理地存在于磁盘上并占用空间。 它们被称为 死行,是所谓 膨胀 的一部分。
请记住
xmin
和xmax
指示交易可见行版本的范围。 此范围不暗示任何直接的时间含义。 XID 的顺序仅反映交易开始事件的顺序。- 在内部,
UPDATE
命令的行为与DELETE
命令,后跟INSERT
命令相同。 - 不会删除任何内容 - 这会导致数据库占用越来越多的磁盘空间。 很明显,这种行为必须以某种方式得到纠正。 下一章将解释
vacuum
和autovacuum
如何完成此任务。
到目前为止,这仅仅是对 MVCC 原理的粗略描述。 该实现考虑了更多问题,例如
ROLLBACK
命令可以撤销更改。- 一段时间后,XID 序列可能会从零开始 (循环)。 在这种情况下,
xmax
可能小于xmin
。
XID 是序列 (在 9.4 之前的 PostgreSQL 版本中,有一个保留值来处理 循环)。 PostgreSQL 了解一些与事务及其 XID 相关的配置参数,其名称类似于 xxx_age,例如:vacuum_freeze_min_age。 对于此类参数,“年龄”不指定时间段,而是表示一定数量的事务,例如,1 亿。
- ↑ PostgreSQL 中的 MVCC [https://postgresql.ac.cn/docs/current/mvcc-intro.html