游乐游手机版
首页/数据库/文章详情

PostgreSQL多版本并发控制核心原理与实现机制

时间:2026-07-22 19:56
PostgreSQL通过xmin和xmax系统字段实现多版本并发控制:更新时标记旧数据的xmax为新事务ID并插入新行,删除时标记xmax。事务状态记录于pg_xact,旧版本由VACUUM进程回收。优势是回滚快且无回滚段限制,劣势是旧版本数据增加扫描开销。

MVCC原理

多版本并发控制(Multi-Version Concurrency Control,MVCC),是数据库中并发访问数据时保证数据一致性的一种方法。简单来说,就是当多个事务同时对同一行数据下手时,怎么保证大家都能得到正确的结果。

场景是这样的:当一个写操作进行到一半,比如刚改了行的前半部分,后半部分还没动,这时另一个读操作恰好插进来。读到的数据就会“一半新一半旧”,这就叫数据一致性问题。你可能会想,用读写锁不就行了——写的时候不让读,读的时候不让写。但这么一来,读写操作就彻底串行化了,并发性能大打折扣。

于是,有人就琢磨出一个既能写又能读的方案,这就是MVCC。它的核心思路很直接:写新数据时,老数据并不直接删除或覆盖。并发读仍然可以读到原来的数据,这就从根本上避免了数据不一致的问题。

实现MVCC的方式主要有两种:

  • 第一种:写新数据时,把原数据挪到一个单独的位置,比如回滚段中。其他用户读数据时,从回滚段里把原数据读出来。
  • 第二种:写新数据时,原数据不动,直接把新数据插入进来。

PostgreSQL用的是第二种,而Oracle和MySQL的InnoDB引擎用的是第一种。两者的实现路径不同,带来的效果也各有千秋,后面会细说。

PostgreSQL中的多版本并发控制

在PostgreSQL中,多版本的实现说白了就是“旧数据留在原地,新数据插进来”。每一张表上都有四个系统字段——xminxmaxcmincmax,它们就是为多版本功能而生的。

字段 类型 含义
xmin xid 插入该行的事务ID(行的“出生证明”)
xmax xid 删除/更新该行的事务ID(0表示未被删除)
cmin cid 插入该行的命令序号(同一事务内的第几条语句)
cmax cid 删除该行的命令序号(同一事务内)
ctid tid 表示表内的物理位置(第一个数字表示数据行所在的物理块的物理块号,第二个数字表示数据行在物理块中的行号)

xminxmaxcmincmax这四个字段,在多版本实现中就是用来判断数据行是否对用户可见的。PostgreSQL将修改前后的数据存储在相同的结构中,具体分为以下几种情况:

  • 新插入一行时,将新插入行的xmin填写为当前的事务ID,xmax填“0”。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

  • 修改这一行时,实际上是新插入一行。原数据行上的xmin不变,xmax改为当前的事务ID;新数据行上的xmin填为当前的事务ID,xmax填“0”。

如下图所示,lp=2lp=3t_ctid 都是 (0,3)。这说明 UPDATE 操作产生的新旧版本共享同一个 ctid 槽位引用(新版本指向自身,旧版本的 t_ctid 在 UPDATE 时未被修改),但它们是两个独立的物理元组,通过 xmin/xmax 形成版本链。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

通过 pageinspect 插件直接读取页面原始内容,可以查看旧行数据:

SELECT
    lp,              -- 行指针编号 (Line Pointer)
    t_ctid,          -- 元组的 ctid
    t_xmin,          -- 插入事务ID
    t_xmax,          -- 删除/更新事务ID
    t_infomask2,     -- 属性标志位(含 cmin/cmax)
    t_hoff,          -- 头部长度
    t_data           -- 原始用户数据(bytea 十六进制)
FROM heap_page_items(get_raw_page('t1', 0));

PostgreSQL中多版本MVCC的核心原理与实现机制详解

  • 删除一行时,把被删除行上的xmax填写当前的事务ID。注意,xmax不等于0并不一定就是死元组。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

从上面的叙述中不难发现:xmin就是标记插入数据行的事务ID,而xmax就是标记删除数据行的事务ID。PostgreSQL没有纯粹的“修改”操作,因为修改数据行,实际上就是把原数据行上的xmax标记上自己的事务ID(相当于打上删除标记),然后再新插入一条记录。所以最新版本的数据,就是xmax为0的那一条。

当两个事务同时访问同一行记录时,系统通过检查xminxmax的标记来判断记录的版本,再根据版本号与当前事务标识进行比较,确定自己的数据权限。

当删除数据时,记录并不会从数据块中立即消失,空间也不会马上释放。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

PostgreSQL中多版本MVCC的核心原理与实现机制详解

所以,PostgreSQL的多版本实现中,首先要解决的就是原数据的空间释放问题。PostgreSQL通过运行VACUUM进程来回收之前的存储空间。默认情况下,PostgreSQL的AutoVacuum是打开的,也就是说,当一个表的更新量达到一定阈值时(死元组数 > 50 + (0.2 × 活行数)),AutoVacuum会自动回收空间。当然,也可以关闭AutoVacuum进程,然后在业务低峰期手动运行VACUUM命令来回收空间。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

这里有一个有意思的问题:在PostgreSQL中,如果一个事务执行失败,该事务在数据文件中产生的数据并不会在事务回滚时被清理。为什么?为什么不在事务提交时把这些数据标记为有效,而在事务回滚时标记为无效呢?

答案是效率。如果事务提交或回滚时都要再次标记数据,那这些数据就有可能被刷新到磁盘中,导致另一次I/O,从而降低性能。

那么,系统如何知道这些数据是有效还是无效呢?PostgreSQL是通过记录事务的状态来实现的。数据行上记录了xminxmax,只需知道xminxmax对应的事务是成功提交还是回滚了,就可以判断这些数据行是否有效。

PostgreSQL把事务状态记录在pg_xact中(旧版本中被称为pg_clog),在数据目录的pg_xact子目录下。

PostgreSQL中多版本MVCC的核心原理与实现机制详解

不过pg_xact是一个二进制文件目录,不能直接查询,可以通过内置函数txid_status()来确定事务是提交还是回滚:

committed:  事务已成功提交
aborted:    事务已回滚(中止)
in progress: 事务仍在运行中
null:       事务 ID 太老,状态信息已被清理(见下文“事务 ID 回卷”)

PostgreSQL中多版本MVCC的核心原理与实现机制详解

PostgreSQL中多版本MVCC的核心原理与实现机制详解

PostgreSQL多版本的优劣分析

Oracle和MySQL的InnoDB引擎也都实现了多版本功能,但实现方式与PostgreSQL不同。在这两个数据库中,旧版本的数据并不记录在原先的数据块中,而是被记录在回滚段中。如果要读取旧版本的数据,需要根据回滚段的数据重构旧版本。

相比之下,PostgreSQL的多版本机制更像是Ja va虚拟机中的垃圾回收机制。事务提交前,只需要访问原来的数据即可;提交后,系统更新元组的存储标识,直到VACUUM进程回收为止。

相对于InnoDB和Oracle,PostgreSQL的多版本优势在于:

  • 事务回滚可以立即完成,无论事务进行了多少操作。
  • 数据可以进行大量更新,不必像Oracle和InnoDB那样需要经常保证回滚段不会被用完,也不会像Oracle那样经常遇到“ORA-1555”错误的困扰。

相应地,劣势也很明显:

  • 旧版本数据需要清理。PostgreSQL清理旧版本称为VACUUM,并提供了VACUUM命令进行清理。
  • 旧版本的数据会导致查询更慢一些,因为旧版本的数据存储于数据文件中,查询时需要扫描更多的数据块。

以上就是PostgreSQL中多版本MVCC的核心原理与实现机制详解。理解这些机制,对于数据库调优和故障排查都大有裨益。

来源:https://www.jb51.net/database/367191c2j.htm
上一篇Linux下MySQL配置文件与常用选项图文详解 下一篇SQL Server varchar 中文长度校验问题解决方案
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
自增主键值从何而来?深入理解原理,告别只会auto_increment
数据库 · 2026-07-25

自增主键值从何而来?深入理解原理,告别只会auto_increment

KingbaseES推荐使用serial、bigserial、显式sequence或identity列实现自增主键。serial创建integer并关联序列,bigserial对应bigint;显式sequence可自定义起始值等参数;identity有generatedbydefault(允许指定值)与always(禁止)两种模式。

Linux下瀚高数据库授权文件过期及替换解决方案
数据库 · 2026-07-25

Linux下瀚高数据库授权文件过期及替换解决方案

在银河麒麟系统下,瀚高数据库hgdb-4 5试用授权20天到期后需替换正式授权文件。正确操作:停止服务,备份旧文件,将授权文件复制到 opt highgo hgdb-4 5 etc lic 并命名为hgdb lic,设置权限600和属主highgo:highgo,再启动服务。禁止直接修改data目录下的license info文件。

Oracle BLOB实时同步的5大技术挑战与难点解析
数据库 · 2026-07-25

Oracle BLOB实时同步的5大技术挑战与难点解析

OracleBLOB实时同步面临分片组装、多列隔离、长事务跨窗口、事务回滚及大对象资源控制等技术挑战,必须在日志中精确还原完整字段值,才能保证源端与目标端数据完全一致,这对同步系统的稳健性提出了高要求。

MySQL禁用redo日志导致全备失败
数据库 · 2026-07-25

MySQL禁用redo日志导致全备失败

MySQL全量备份失败是由于数据定义语言操作触发排序索引构建,禁用重做日志导致XtraBackup无法获取一致性备份。测试验证表明,优化表语句即使无数据也会触发该问题。根本原因在于排序索引构建过程跳过了重做日志记录,破坏了备份的一致性。

Kafka架构图优化与改进的全面详细步骤与实践指南
数据库 · 2026-07-25

Kafka架构图优化与改进的全面详细步骤与实践指南

Kafka作为实时数据流处理的核心中间件,其底层架构虽已相当成熟,但在实际生产环境中,要充分发挥其性能潜力,仍需落实到具体的调优与架构改造上。核心目标可归纳为三点:如何承载更高的吞吐量、如何保障数据不丢失、以及故障发生时如何快速恢复。本文将从这几个关键方向出发,深入探讨如何真正榨干Kafka集群的性