MySQL 中多版本并发控制(MVCC),是现代数据库引擎实现中常用的处理读写冲突的手段,MVCC 作为 MySQL 高级应用特性,目的在于提高数据库高并发场景下的吞吐性能。
事务的4个隔离级别以及对应的3种异常:
在 MySQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,但不能解决幻读问题。如果我们想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。
而MVCC就是通过乐观锁的方式来解决不可重复读和幻读问题,它可以在大多数情况下替代行级锁,降低系统的开销。
MySQL 并发事务会引起更新丢失问题,解决办法是锁,主要分两类:
MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。
我们可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果。
通过 MVCC 我们可以解决以下几个问题:
(1)读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
(2)降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
(3)解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。
解释一下可能难以理解的几个词汇:
为了更好地让大家理解MVCC,我们用一个示例场景来说明。
假设有个账户金额表 user_balance,包括三个字段,分别是 username 用户名、balance 余额和 bankcard 卡号,表数据如下所示:
用户 A 和用户 B 之间进行转账,此时数据库管理员想要查询 user_balance 表中的总金额,两个场景存在并发情况,在没有MVCC的情况下,会出现哪些问题呢。
Case1:因为需要采用加行锁的方式,用户 A 给 B 转账时间等待很久,如下图所示。
Case2:当我们读取的时候用了加行锁,可能会出现死锁的情况,如下图所示。
比如当我们读到 A 有 1000 元的时候,此时 B 开始执行给 A 转账。
当查询一条记录的时候,执行流程如下:
相关概念
1. 事务版本号
一个自增长的事务ID,用于标记事务执行的先后顺序。
2. Read View
在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?
这时就需要用到 Read View 了,它帮我们解决了行的可见性问题。Read View 保存了当前事务开启时所有活跃(还没有提交)的事务列表,换个角度,可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表。
Read VIew 中的几个重要属性:
3. 行记录的隐藏列
InnoDB 的叶子节点段存储了数据页,数据页中保存了行记录,在这些行记录中有一些重要的隐藏字段:
4. 聚集索引
聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。一个表只能有一个聚集索引,因为一个表的物理顺序只有一种情况,所以,对应的聚集索引只能有一个。
5. Undo Log
InnoDB 将行记录快照保存在 Undo Log,可以在回滚段中找到它们,主要用于记录数据被修改之前的日志,在对表信息做修改之前先会把数据拷贝到Undo Log里,当事务进行回滚时可以通过Undo Log里的日志进行数据还原。
回滚段中回滚指针间关联关系,如下图所示:
1、在读已提交的情况下,即使采用了 MVCC 方式也会出现幻读
我们同时开启事务 A 和事务 B,先在事务 A 中进行某个条件范围的查询,读取的时候采用排它锁,在事务 B 中增加一条符合该条件范围的数据,并进行提交,然后我们在事务 A 中再次查询该条件范围的数据,就会发现结果集中多出一个符合条件的数据,这样就出现了幻读。
出现幻读的原因是在读已提交的情况下,InnoDB 只采用记录锁(Record Locking)。
InnoDB 三种行锁的方式:
2、在可重复读的情况下,InnoDB 可以通过 Next-Key 锁 +MVCC 来解决幻读问题。
想插入球员艾利克斯·伦(身高 2.16 米)的时候,事务 B 会超时,无法插入该数据。
这是因为采用了 Next-Key 锁,会将 height>2.08 的范围都进行锁定,就无法插入符合这个范围的数据了。然后事务 A 重新进行条件范围的查询,就不会出现幻读的情况。
MVCC 的核心就是 Undo Log+ Read View。
同时针对不同的隔离级别,Read View 的生成策略不同,也就实现了不同的隔离级别。