MVCC多版本并发控制

mvcc可以保证RR隔离级别下一致性读(consistent read)的问题,也解决了读写冲突的问题。

读写冲突的来源

没有mvcc控制的情况下,假如我们的场景是读远大于写的场景,那么事务A对某读频繁的表id为1的行进行更新,根据两阶段锁协议,那么该行是会加一个X锁,且没有释放,这样其他事务在读取的时候会被锁上,导致并发能力降低。

而mvcc允许行被加锁的情况下,读取旧版本数据。

一致性读

innodb的一致性读通过一致性读视图及undo log实现。

Undo Log

数据库中的每一行都保存这不同的版本,旧版本保存在undo log中。

当一行数据被创建时,会有一些额外的数据,如下图(其中rowid只会在没有主键情况下生成,一般某个表没有指定主键innodb会选择第一个唯一索引作为主键,如果在没有会隐式的生成一列rowid):

当改行数据被修改时,

为该行记录生成undo log, 新纪录的db_roll_pt指针指向该条记录。

undo logs会有一个单独的purge线程负责清除,保证undo logs不会无限增长。

undo logs会有一些参数控制:

innodb_undo_directory:将undo log存储位置独立出来,默认位置为安装目录。

inndb_undo_tablespace:mysql初始化时分配的表空间个数,默认为128,之后会在data目录下创建制定个数的undo文件,文件名以undo_000,undo_001递增。(8.0.14版本后不再支持,具体undo tablespace的介绍,看详情)

(之所以mysql可以把undo log存储独立出来,原因是可以把日志部署到ssd上,提高事务的并发读。)

undo log都是保存在undo log segment中,undo log segment 保存在rollback segment中。

每个回滚段包含1024个undo log segment, 而mysql最大支持128个回滚段(innodb_rollback_segments参数可以显示控制)。因为每个事务在读写undo log时都要独占一块undo log segment,所以理论上最大支持1024 * 128个并发事务操作。

 

一致性读的实现

RR级别下,数据可见性的规则就是,事务启动前已经提交的事务的更改可见,事务启动后的事务提交不可见。(begin/start transaction并不是一个事务的起点,在该指令后的第一个操作标的语句才标识着事务启动。)

每个事务在启动时会获得一个递增的事务id,记trx_id,并且会创建一个read-view的struct,该结构体包含一个当前所有活动事务的id集合(包括当前事务),一个最小活动事务id和一个最大活动事务id,之前提到过,每一行数据都有一个额外的db_trx_id, 读取到该行时,如果该db_trx_id与当前事务id不一致,就开始读取undo log,然后利用undo log和活动事务集合判断出可以读取的值,判断逻辑图如下:

以上概念称为一致性读,与之对应的有一个”当前读”, 即:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)

举个例子:

k初始为1。

那么B再去查询时结果为3,这就是当前读。

发表评论

电子邮件地址不会被公开。 必填项已用*标注