PostgreSQL中MVCC实现机制
MVCC原理多版本并发控制Multi-Version Concurrency Control MVCC 是数据库中并发访问数据时保证数据一致性的一种方法。在并发操作中 当正在写时 如果有用户在读 这时写可能只写了一半 如一行的前半部分刚写入 后半部分还没有写入 这时可能读的用户读取到的数据行的前半部分数据是新的 后半部分数据是原来的 这就导致了数据一致性问题。解决这个问题的最简单的方法是使用读写锁 写的时候不允许读 正在读的时候也不允许写 但这种方法会导致读和写的操作不能并发执行。于是 有人想到了一种能够让读写并发执行的方法 这种方法就是MVCC。MVCC方法是写数据时 原数据并不删除 并发的读还能读到原数据 这样就不会有数据一致性问题了。实现MVCC的方法有以下两种第一种 写新数据时 把原数据移到一个单独的位置 如回滚段中 其他用户读数据时 从回滚段中把原数据读出来。第二种 写新数据时 原数据不删除 而是把新数据插入进来。PostgreSQL数据库使用的是第二种方法 而Oracle数据库和MySQL数据库中的InnoDB引擎使用的是第一种方法。PostgreSQL中的多版本并发控制PostgreSQL中的多版本实现是通过把原数据留在数据文件中 新插入一条数据来实现多版本的功能的。每张表上都有4个系统字段“xmin”“xmax”“cmin”“cmax” 这4个字段就是为多版本的功能而添加的。字段类型含义xminxid插入该行的事务ID行的出生证明xmaxxid删除/更新该行的事务ID0表示未被删除cmincid插入该行的命令序号同一事务内的第几条语句cmaxcid删除该行的命令序号同一事务内ctidtid表示表内的物理位置第一个数字表示数据行所在的物理块的物理块号 第二个数字表示数据行在物理块中的行号xmin、 xmax、 cmin、 cmax这4个字段在多版本实现中用于控制数据行是否对用户可见。PostgreSQL将修改前后的数据存储在相同的结构中 分为以下几种情况新插入一行时 将新插入行的xmin填写为当前的事务ID xmax填“0”。修改这一行时 实际上新插入一行 原数据行上的xmin不变 xmax改为当前的事务ID 新数据行上的xmin填为当前的事务ID xmax填“0”。如下图所示lp2 和 lp3 的 t_ctid 都是 (0,3)。这说明 UPDATE 操作产生的新旧版本共享同一个 ctid 槽位引用新版本指向自身旧版本的 t_ctid 在 UPDATE 时未被修改但它们是两个独立的物理元组通过 xmin/xmax 形成版本链。通过 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));删除一行时 把被删除行上的xmax填写当前的事务IDxmax不等于0不一定是死元组从上面的叙述中就可以知道 xmin就是标记插入数据行的事务ID 而xmax就是标记删除数据行的事务ID。没有修改数据行的操作 因为修改数据行 实际上就是把原数据行上的xmax标记上自己的事务ID相当于打上删除标记 然后再新插入一条记录。所以最新版本的数据即xmax为0的一条。当两个事务同时访问记录时 通过参考xmin和xmax的标记判断记录的版本 根据版本号与自己当前的事务标识进行比较 确定自己的数据权限。当删除数据时 记录并没有从数据块中被删除 空间也没有立即释放。PostgreSQL的多版本实现中首先要解决的是原数据的空间释放问题。PostgreSQL通过运行Vaccum进程来回收之前的存储空间 默认PostgreSQL数据库中的AutoVacuum是打开的 也就是说 当一个表的更新量达到一定值时死元组数50(0.2×活行数 AutoVacuum自动回收空间。当然也可以关闭AutoVacuum进程 然后在业务低峰期手动运行VACUUM命令来回收空间。在PostgreSQL中 若一个事务执行失败 在数据文件中该事务产生的数据并不会在事务回滚时被清理掉。 为什么要这样做呢 为什么不在事务提交时把这些数据标记成有效而在事务回滚时把这些数据标记成无效呢这是出于效率的考虑。若事务提交或回滚时再次标记数据 那这些数据就有可能会被刷新到磁盘中 再次标记会导致另一次I/O 从而降低性能。那么如何知道这些数据是有效还是无效呢PostgreSQL通过记录事务的状态来实现。数据行上记录了xmin和xmax 只需了解xmin和xmax对应的事务是成功提交还是回滚了 就可以知道这些数据行是否有效。PostgreSQL把事务状态记录在pg_xact旧版本中被称为pg_clog在数据目录的pg_xact子目录下但是pg_xact是一个二进制文件目录不能直接查询可以通过内置函数 txid_status()确定事务是提交还是回滚committed: 事务已成功提交 aborted: 事务已回滚中止 in progress: 事务仍在运行中 null: 事务 ID 太老状态信息已被清理见下文“事务 ID 回卷”PostgreSQL多版本的优劣分析Oracle数据库和MySQL数据库的InnoDB引擎也都实现了多版本的功能 但它们与PostgreSQL的实现方式是不一样的 在这两个数据库中 旧版本的数据并不记录在原先的数据块中 而是被记录在回滚段中 如果要读取旧版本的数据 需要根据回滚段的数据重构旧版本数据。PostgreSQL的多版本机制与Java虚拟机的垃圾回收机制比较相像。事务提交前 只需要访问原来的数据即可 提交后 系统更新元组的存储标识 直到Vaccum进程收回为止。相对于InnoDB和Oracle PostgreSQL的多版本的优势在于以下几点事务回滚可以立即完成 无论事务进行了多少操作。数据可以进行很多更新 不必像Oracle和InnoDB那样需要经常保证回滚段不会被用完 也不会像Oracle数据库那样 经常遇到“ORA-1555”错误的困扰。相对于InnoDB和Oracle PostgreSQL的多版本的劣势在于以下几点旧版本数据需要清理。 PostgreSQL清理旧版本称为VACUUM 并提供了VACUUM命令进行清理。旧版本的数据会导致查询更慢一些 因为旧版本的数据存储于数据文件中 查询时需要扫描更多的数据块。