MVCC

MVCC

基本原理

MVCC 的实现方法有两种:

  1. 写新数据时,把旧数据移到一个专门的地方(如回滚段),其他人读数据时,从回滚段中把旧数据读出来。

  2. 写数据时,旧数据不删除,把新数据插入。这种又叫快照隔离 Sanpshot Isolation (简称SI)

PostgreSQL 使用的是第二种方法,Oracle 数据库和 MySQL InnoDB 引擎使用一种。

实现方法

事务ID

PostgreSQL 中,每个事务都有一个唯一的事务ID,被称为 XID

数据库中的事务ID递增。可通过 txid_current() 函数获取当前事务的ID。

隐藏的多版本标记字段

PostgreSQL中,对于每一行数据(称为一个tuple),包含有4个隐藏字段。这四个字段是隐藏的,但可直接访问。

  • xmin 在创建记录时,记录此值为插入tuple的事务ID

  • xmax 在删除tuple时,记录事务ID

  • cmincmax 标识在同一个事务中多个语句命令的序列值,从0开始,用于同一个事务中实现版本可见性判断

MVCC保证原子性

  • 对于插入操作,PostgreSQL会将当前事务ID存于 xmin 中。

  • 对于删除操作,其事务ID会存于 xmax 中。

  • 对于更新操作,PostgreSQL会将旧数据标记为删除状态,写入一条新数据。将当前事务ID存于旧数据的 xmax 中,并存于新数据的 xin 中。

  • 事务对增、删和改所操作的数据上都留有其事务ID,可以很方便的提交该批操作或者完全撤销操作,从而实现了事务的原子性。

MVCC可重复读

相对于提交读,重复读要求在同一事务中,前后两次带条件查询所得到的结果集相同。实际中,PostgreSQL的实现更严格,不仅要求可重复读,还不允许出现幻读。它是通过只读取在当前事务开启之前已经提交的数据实现的。结合上文的四个隐藏系统字段来讲,PostgreSQL 的可重复读是通过只读取 xmin 小于当前事务ID且已提交的事务的结果来实现的。

优点

  • 使用MVCC,读操作不会阻塞写,写操作也不会阻塞读,提高了并发访问下的性能

  • 事务的回滚可立即完成,无论事务进行了多少操作

  • 数据可以进行大量更新,不像MySQL和Innodb引擎和Oracle那样需要保证回滚段不会被耗尽

缺点

  • 事务ID个数有限制。它是个 int32,支持大约40亿个事务,当事务ID用完时,会出现wraparound问题

  • 大量过期数据占用磁盘并降低查询性能

  • 解决方法:VACUUM

    • 可用的有效最小事务ID为3。VACUUM 时会将所有已提交的事务ID均设置为2,即 frozon 状态,这样 wraparound 也不会有问题

    • VACUUM 简单的将 dead tuple 对应的磁盘空间标记为可用状态,新的数据可以重用这部分磁盘空间。但不会被真正释放、还给操作系统

    • VACUUM FULL 通过 标记-复制 的方式将所有有效数据复制到新的磁盘文件,并释放旧的磁盘空间

参考

SQL优化(六) MVCC PostgreSQL实现事务和多版本并发控制的精华

Last updated