1. 引言
根据 Vadim Mikheev 的说法,PostgreSQL 的多版本并发控制(MVCC)是一种"在多用户环境中提高数据库性能的高级技术"。该技术要求系统中存在同一数据元组的多个"版本",这些版本由不同时间段内获取的快照进行管理。换句话说,在这种技术下,PostgreSQL 需要根据多个参数(如获取的快照、当前事务 ID 等)来判断哪些元组对用户是"可见"的,哪些是不可见的。这也被称为 PostgreSQL 的"可见性检查"规则。在本文中,我将讨论可见性检查的基本原理,帮助您了解 PostgreSQL 内部是如何执行这项任务的,希望这对您的开发工作有所帮助。
2. 涉及的参数
- 元组本身,包含以下信息:
- xmin(插入该元组的事务 ID)
- xmax(如果大于 0,表示删除该元组的事务 ID;否则表示未被删除)
- cid(命令 ID)
- hintbit(提示位)
- 全局顶级事务 ID(如果存在)
- 当前快照,包含 xmin、xmax 和 cid
- 提交日志数据(CLOG)
3. 检查过程
3.1 检查提示位(Hintbit)
可见性检查过程从检查提示位(hintbit)开始。如果提示位的状态为"已提交"(COMMITTED),则可以跳过大部分其他可见性检查规则以提高效率。对于已提交的元组,PostgreSQL 会获取该元组的 xmin 值,并与当前快照进行比较,以确保该元组当前未处于"进行中"(in progress)状态(参见 3.3 节中的公式以确定"进行中"状态)。这一检查是必要的,因为即使元组已提交,仍有可能被其他后台进程在此时更新。如果元组未被其他后台进程"进行中",则最后会检查其 xmax 值以确保它是无效的(即未被删除)。当上述条件都满足时,该元组被认为是对用户可见的。
如果提示位显示"已中止"(ABORTED),则该元组对用户不可见。如果提示位没有值,则 PostgreSQL 会继续执行后续的可见性检查规则。
3.2 检查元组的 xmin 是否等于全局顶级事务 ID
下一个检查步骤是将元组的 xmin 与全局顶级事务 ID 进行比较,看它们是否相等。全局顶级事务 ID 仅在用户通过 BEGIN
语句手动启动事务时才会被设置。如果用户未以这种方式启动事务(即未发出 BEGIN
语句),则全局顶级事务 ID 不会被设置,因此此检查将被跳过。
如果元组的 xmin 等于全局顶级事务 ID,则意味着该元组当前由当前后台进程(而非其他进程)"进行中"。此时,命令 ID(cid)将用于确定可见性。在事务块中,发出的每个命令都有一个关联的命令 ID,用于指示命令的先后顺序。例如,如果在一个事务中,SELECT
命令在 UPDATE
命令之后,那么 SELECT
必须看到由之前的 UPDATE
命令更新的新元组,cid 在这一确定中起作用。这种行为还受"隔离级别"(isolation level)的影响,但隔离级别不在本文讨论范围内。默认的隔离级别是"读已提交"(READ COMMITTED),这使得 SELECT
能看到由之前的 UPDATE
更改的数据;但如果隔离级别设置为"可重复读"(REPEATABLE READ),则 SELECT
不会看到由之前的 UPDATE
更改的数据。请牢记这一点。
如果元组的 xmin 不等于全局顶级事务 ID,则 PostgreSQL 会继续执行后续的可见性检查规则。
3.3 检查当前快照
下一个检查步骤是 PostgreSQL 检查该元组是否被其他后台进程"进行中"。这是通过将元组的 xmin 值与当前快照的 xmin 和 xmax 值进行比较来完成的,公式如下:
- 元组 xmin < 快照 xmin:表示未进行中
- 元组 xmin >= 快照 xmax:表示进行中
如果根据快照判断该元组为"进行中",则该元组对当前用户不可见,因为其他后台进程仍在处理它。如果元组未被认为"进行中",则 PostgreSQL 会继续执行后续的可见性检查规则。
3.4 检查提交日志(CLOG)
下一个检查步骤是 PostgreSQL 获取元组的 xmin 值,并对照提交日志(CLOG)检查该元组是否已提交或中止。CLOG 类似于一个事务 ID 数组,每个数组元素存储一个提交状态。PostgreSQL 使用公式将事务 ID 转换为 CLOG 块加上偏移量,以精确访问正确的 CLOG 元素。CLOG 数据结构会通过检查点(checkpoint)进程定期刷新到磁盘,存储在以下三个目录中:pg_xact
、pg_multixact
和 pg_subtrans
。
如果 CLOG 显示该元组已提交,PostgreSQL 会继续检查元组的 xmax 值以确保它是无效的(即未被删除)。如果 xmax 无效,则该元组对用户可见。同时,PostgreSQL 会将该元组的提示位更新为"已提交"(COMMITTED),以便在下次可见性检查时无需再次访问 CLOG,因为访问 CLOG 的成本较高。
如果 CLOG 显示该元组已中止或无效,则该元组对当前用户不可见。同时,PostgreSQL 会将该元组的提示位更新为"无效"(INVALID)。
4. 总结
以上是我对 PostgreSQL 中基本可见性规则的理解。当然,还有其他复杂的检查未在此提及,例如子事务和多事务的处理,但它们的模式与本文提到的内容有些相似。