微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。
前言
之前在 面试必问的 MySQL,你懂了吗?中简单的介绍了 MVCC 的原理,掌握了这个原理其实在面试时是可以加分不少的。
因为现在很多人的理解还是停留在《高性能 MySQL》书中的版本,也就是通过创建版本号和删除版本号来判断。这个时候如果你能给出正确的理解,则会让面试官眼前一亮,这也是我们在面试中凸显出“自己和其他候选者不一样的地方”,会更有利于在众多候选者中脱颖而出。
本文在此基础上,对 MVCC 展开详细的分析,同时修改了之前的一些不太准确的说法,希望可以助你在面试中更好的发(zhuang)挥(bi)。
PS:本文的源码基于MySQL 8.0.16,对于现阶段生产环境常用的 5.7.* 版本,MVCC 部分的源码基本相同,因此可以放心参考。而 5.6.* 则有比较大的不同,主要是一些数据结构都改变了,但是究其核心原理还是基本一致的。
基础概念
并发事务带来的问题(现象)
脏读:一个事务读取到另一个事务更新但还未提交的数据,如果另一个事务出现回滚或者进一步更新,则会出现问题。
不可重复读:在一个事务中两次次读取同一个数据时,由于在两次读取之间,另一个事务修改了该数据,所以出现两次读取的结果不一致。
幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
要解决这些并发事务带来的问题,一个比较简单粗暴的方法是加锁,但是加锁必然会带来性能的降低,因此 MySQL 使用了 MVCC 来提升并发事务下的性能。
MVCC 带来的好处?
试想,如果没有 MVCC,为了保证并发事务的安全,一个比较容易想到的办法就是加读写锁,实现:读读不冲突、读写冲突、写读冲突,写写冲突,在这种情况下,并发读写的性能必然会收到严重影响。
而通过 MVCC,我们可以做到读写之间不冲突,我们读的时候只需要将当前记录拷贝一份到内存中(ReadView),之后该事务的查询就只跟 ReadView 打交道,不影响其他事务对该记录的写操作。
事务隔离级别
读未提交(Read Uncommitted):最低的隔离级别,会读取到其他事务还未提交的内容,存在脏读。
读已提交(Read Committed):读取到的内容都是已经提交的,可以解决脏读,但是存在不可重复读。
可重复读(Repeatable Read):在一个事务中多次读取时看到相同的内容,可以解决不可重复读,但是存在幻读。但是在 InnoDB 中不存在幻读问题,对于快照读,InnoDB 使用 MVCC 解决幻读,对于当前读,InnoDB 通过 gap locks 或 next-key locks 解决幻读。
串行化(Serializable):最高的隔离级别,串行的执行事务,没有并发事务问题。
InnoDB MVCC 实现
核心数据结构
trx_sys_t:事务系统中央存储器数据结构
struct trx_sys_t {
TrxSysMutex mutex; /*! 互斥锁 */
MVCC *mvcc; /*! mvcc */
volatile trx_id_t max_trx_id; /*! 要分配给下一个事务的事务id*/
std::atomic<trx_id_t> min_active_id; /*! 最小的活跃事务Id */
// 省略...
trx_id_t rw_max_trx_id; /*!< 最大读写事务Id */
// 省略...
trx_ids_t rw_trx_ids; /*! 当前活跃的读写事务Id列表 */
Rsegs rsegs; /*!< 回滚段 */
// 省略...
};
MVCC&#x