MySQL InnoDB实现MVCC

面阿里一面的时候问了我innodb中mvcc的实现,当时并没有回答出全部的细节,只是说了innodb中没有真正的mvcc,是通过undo段来假装实现的,跟理论上的mvcc并不一样,有妥协。面完了突然想到这一点,就去翻了下mysql的Reference Manual,算是做个补遗吧。

定义

老规矩,先解释下定义。MVCC (Multiversion Concurrency Control),就是多版本并发控制的缩写。在数据库中出现的原因是实现事务不同隔离级别的传统的加锁方式性能消耗实在太大,而现实中的很多应用都是读多写少,传统的悲观锁的方式不能满足需求,才引出了今天广泛应用的mvcc。

其基本原理是将数据同版本结合起来,在读的时候可以选择特定的版本,当读写不同版本时可以不用阻塞读。MVCC是一种后验性的,读不阻塞写,写也不阻塞读,等到提交的时候才检验是否有冲突,由于没有锁,所以读写不会相互阻塞,从而大大提升了并发性能。其实我们通常使用的版本管理系统git就是mvcc的,每个人都可以在本地修改,只有在提交时才检测冲突,是一种最终一致性系统。

理论上mvcc实现的是有条件的更新,只有在版本匹配的情况下才能更新数据,避免了长时间的锁定,是一种乐观锁。

InnoDB的实现

innodb的默认事务隔离级别是RR也就是Repeatable-Read。这个隔离级别下重复的读不会见到不同的数据,其实现就是通过给数据行加上不同的版本,实现一致性读。

innodb的数据行具体的格式这里就不列出了,只指出跟今天要解释的相关的两个,一个是6个字节的DB_TRX_ID,另外一个是7个字节的DB_ROLL_PTR。DB_TRX_ID 用来表示不同的事务号,通过这个来实现事务不同的版本。DB_ROLL_PTR就是innodb实现mvcc的主要方式,可以把它理解为一个指针,这个指针指向undo段中的上一个版本的数据。

具体实现多版本的时候就是通过这个版本号和指针来实现的。select操作的时候会使用当前版本号,然后对于每行数据会检查数据的版本号,只有数据的版本号小于select的版本号同时数据还没有被删除的情况下才会读出这行数据。update的时候会新增一行数据,在这行数据上修改,同时将DB_ROLL_PTR指向之前那行的数据,事务回滚的时候就可以通过这行数据来恢复操作前的状态。

所以,通过上面的分析我们发现,innodb并没有实现真正理论上的mvcc,它只是通过DB_ROLL_PTR指向undo段中的数据,没有实现核心的多版本共存,undo段中的数据类似于一个单链表是串行化实现的。之所以做这种实现上的妥协,是因为理想的mvcc对于一次修改多行数据的事物回滚可能会造成丢失更新。

小结

本篇只是简单的介绍了下innodb对mvcc的实现,发现它跟理论上的mvcc还是有不同的地方,只是利用了undo log中的信息而且写操作还是使用的悲观锁。具体的细节参考的是mysql的reference manual,顺便说一句,这里面对mysql介绍的很详细,可惜好多都没有读过 (逃

Reference

InnoDB Multi-Versioning