Database-浅谈Postgresql与Mysql的MVCC实现机制

MVCC介绍

在早期的数据库理论中,一行元组上有两种锁,一种读锁,一种写锁,读会阻塞写,写也会阻塞读,这对于一个并发繁忙的系统来说代价是异常高昂的。
对于传统数据库来说,为了维护数据的一致性和完整性,避免一个事务看到其它并发事务更新而导致看到不一致的数据,通常采用的是LOCK机制,在锁请求无法得到顺利回应时,事务会被阻塞甚至hang死。
为了避免这种代价,MVCC(Multi-Version Concurrency Control),即多版本并发控制的方法被提出。
MVCC可以理解为行级锁的一种变种,其避开传统数据库的锁机制,最大限度的减少了锁的竞争,避免了加锁操作,以数据的多版本或多快照避免了读写之间的阻塞。

MVCC的两种实现方法:
1、当写入新数据时,把原有数据转移到一个单独的地方,如undo段中,其他人读数据时,会读取undo中的旧数据;
2、当写入新数据时,原有数据不删除,而是把新数据插入,在不同版本的原有数据不需要时,垃圾回收器将回收这些过期的数据。

众所周知,Oracle在很久以前就使用了undo来实现了MVCC,这里献上Oracle7中关于MVCC的描述,致敬Oracle!
在这里插入图片描述在这里插入图片描述

Postgresql中MVCC的实现

Postgresql实现MVCC的方式对应着上面的第二条,我们来看它的基本实现方式。
在Postgresql中并没有undo的概念,其文件页中以逻辑方式存放着一个叫Tuple Item space的元组数据空间,每个元组都对应着该逻辑行数据的每一个版本,数据文件中存放着每一逻辑行的多个版本。其通过当前的事务快照和对应的元组版本号来判断该元组的有效性、可见性、可更新性。
我们来总结一下相关概念和原理:
1、每个版本的头部会记录着t_xmin(该版本的创建事务ID)和t_xmax(删除事务ID)、t_cid (包含cmin和cmax两个字段,标识在同一个事务中多个语句命令的序列值,从0开始,用于同一个事务中实现版本可见性判断)等信息来唯一定义元组的版本,每次对元组的更改都会产生新版本的元组,版本之间会形成一条版本链,在版本链中,指向是由旧到新。
2、对于插入数据来说,当一个事务插入一条数据时,postgresql会将该事务的XID存储到行版本头部的xmin中,在commit之前,该事务插入的数据对于其他事务是不可见的。
对于更新和删除操作,与更新不同的是它们会将XID存储到行版本的xmax中。
事务只能读取比该事务XID小,即读取xmin小于该XID,并且已经提交的行版本数据。
3、事务都有一个递增的序列号,即事务ID(XID);
4、为了实现元组对事务的可见性判断,引入了事务快照SnapshotData的概念,在事务快照的数据结构中同样也有xmin、xmax字段,但其存储的是事务的运行状况信息。
事务快照的创建过程主要如下:
a、查看所有未提交并活跃的事务记录到数组;
b、选取数组中这些事务中最小的XID存储到该快照的xmin字段;
c、选取数组中这些事务中最大的XID加1后存储到该快照的xmin字段;

其可见性判断逻辑分为:
当前事务XID<xmin时,说明当前事务已提交,对当前快照可见;
当前事务XID>=xmax时,说明事务正在执行,对当前快照不可见;
当前事务XID在xmin和xmax之间时,需结合活跃事务列表与事务提交日志CLOG来判断可见性。

图片来自阿里数据库内核月报
图片来自阿里数据库内核月报

详细可参考http://mysql.taobao.org/monthly/2017/10/01/和pg源码。

MySQL的InnoDB中MVCC的实现

Mysql的MVCC是在Innodb存储引擎中得到支持的。
MVCC在Mysql的InnoDB引擎中就是指在读已提交READ COMMITTD)和可重复读(REPEATABLE READ)隔离级别下的事务对于读操作访问版本链中的记录来得到正确数据的过程。

Innodb为每行记录的后面添加了三个隐藏的字段:
◆DB_TRX_ID:事务ID,对应着最后一次修改(insert、update)该行记录的事务id。
◆DB_ROLL_PTR:回滚指针,当对聚簇索引记录修改的时候,会将老版本写入undo中。这个DB_ROLL_PTR实际上存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。
◆DB_ROW_ID:在没有定义主键时,InnoDB存储引擎会帮我们自动生成聚簇索引,DB_ROW_ID就是它的主键。如果定义了主键,那么聚簇索引中就不会包含DB_ROW_ID。

在InnoDB中是通过read view实现可见性判断的,InnoDB中read view对应着Postgresql的事务快照SnapshotData的概念。
read view就是在某一时刻给事务系统trx_sys打快照,即把trx_sys状态中活跃着的读写事务记录下来,之后的所有读操作根据其事务ID(DB_TRX_ID)与read view中的trx_sys的状态作比较,以此判断事务的可见性。
在查询一行数据时,首先会用read view判断该行是否可见,如果可以,就会顺利访问,如果不可见就会使用undo log来构建历史版本,再进行判断,直到最旧的版本或者可见性条件满足。

read view快照中保存的trx_sys状态主要包括:
up_limit_id:保存着当前trx_sys状态中DB_TRX_ID的最小值;
low_limit_id:创建read view快照时的max_trx_id。

结合以上所述,可见性判断逻辑如下:
1、如果行记录上的DB_TRX_ID小于read view的up_limit_id,则说明该行的最后修改在read view快照创建之前,因此该行记录可见;
2、如果行记录上的DB_TRX_ID大于等于read view的low_limit_id,则说明该行的最后修改在read view快照创建之后,所以该行记录不可见;
3、如果行记录上的DB_TRX_ID在up_limit_id和low_limit_id之间,且DB_TRX_ID在read view存储的读写事务之中,则表示这条记录的最后修改是在readview创建之时,被另外一个活跃事务所修改,所以该行记录不可见。如果DB_TRX_ID不在read view存储的读写事务之中,则表示这条记录的最后修改在readview创建之前,所以可以看到。
4、基于以上三条逻辑如果仍不可见,则会使用undo去构建旧版本,直到找到可以被看见的记录或者解析完所有的undo。
详细可参考http://mysql.taobao.org/monthly/2017/12/01/

Postgresql和Mysql中的垃圾回收

Mysql中垃圾回收是由Purge线程来完成的,在5.6之后这个工作是多线程并行执行的。
Purge线程的主要工作:
1、数据页内标记的删除操作需要从物理上删除,为了提高删除效率和空间利用率,由后台Purge线程解析undolog定期批量清理。
2、当数据页上标记的删除记录已经被物理删除,同时undo所对应的记录已经能被所有事务看到,这个时候undo就没有存在的必要了,因此Purge线程还会把这些undo给truncate掉,释放更多的空间。

在Postgresql中对应的则是vacuum。
目前,vacumm对单表只能串行的来进行清理操作,对于大表来说,会造成性能上的下降。
vacuum另一个令人诟病的是事务ID回卷的问题,这个与其事务ID位数有关,PG的事务ID
是32位(mysql中是64位),当事务ID到达32位的限制时,会重置所有事务ID,这也就意味着要冻结所有数据,而冻结的操作势必耗费更多的IO和CPU的资源。在一个非常繁忙的库中,如果冻结操作需要耗费很多时间而事务ID又被耗尽,数据库就会拒绝所有需要执行的事务,导致数据库不可用。在9.6版本之后,这一点有所改善,但要更好的解决该问题PG还有很长的一段路要走。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值