文章目录
-
- 一、MVCC的由来
- 二、MVCC的实际应用
-
- RR级别场景
- RC级别场景
- 三、MVCC的实现
-
- 3.1 旧版本数据从哪里来——Undo Log
-
- 3.1.1 插入操作对应的undo log
- 3.1.2 删除操作对应的undo log
- 3.1.3 更新操作对应的undo log
- 3.2 多版本数据如何关联——行记录隐藏字段和版本链
- 3.3 不同版本数据如何正确访问——Read View访问规则
-
- 3.3.1 Read View
- 3.3.2 覆盖索引下的MVCC
- 四、扩展一——MVCC的多版本是否影响性能
- 五、扩展二——RR级别能否完全避免幻读问题
- 总结
一、MVCC的由来
我们知道:标准的数据库事务是要具备ACID特性的。但是对于【I】隔离性而言,可以根据不同的场景,权衡使用不同的隔离级别。在MySQL中实现了SQL标准中的四大隔离级别,同时对应了可能会出现的不同问题:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED(读未提交) | 可能 | 可能 | 可能 |
READ COMMITTED(读已提交) | 不可能 | 可能 | 可能 |
REPEATABLE READ(可重复读) | 不可能 | 不可能 | *可能 |
SERIALIZABLE(可串行化) | 不可能 | 不可能 | 不可能 |
其中,需要特别注意的一点是:也是与SQL标准规定不一样的一个地方,MySQL中RR可重复读的隔离级别下,是基本上不会出现幻读问题(这个地方会出现一些特殊的场景,后面会介绍)。
访问MySQL数据一般就是增删改查操作,对应的是insert、delete、update和select语句。
对于insert、delete、update这三种类型的语句而言,需要先定位到最新的数据;对于select语句,也有select … lock in share mode(MySQL8.0中增加了SELECT … FOR SHARE)/select… for update的操作,同样需要读取最新的数据,同时必须堵塞其他并发事务修改当前记录,所以需要进行加锁。 这些操作在MySQL中统一被称为——锁定读(Locking Reads),又称为当前读。
而对于普通的select语句,MySQL则根据不同的隔离级别,进行不同的处理:
- READ UNCOMMITTED(读未提交):读取最新的记录
- READ COMMITTED(读已提交):读取最新提交事务的记录
- REPEATABLE READ(可重复读):读取当前事务在开始前已提交的记录或者被自己事务修改的记录
- SERIALIZABLE(可串行化):读取最新的记录,同时也加上读锁,事务提交前不允许被其他事务修改,也就是select … lock in share mode
可以看到,对于READ COMMITTED(读已提交)和 REPEATABLE READ(可重复读)这两种隔离级别来说,普通的select是不需要加锁的,并且受到事务发生时机的影响。可以利用事务保存的历史版本数据,控制版本访问权限,这种方案就称之为——MVCC(Multi-Version Concurrency Control),而MVCC下的select查询,被称为——非锁定读(Consistent Nonlocking Reads),又称为快照读。
生成的快照就被称之为ReadView(一致性视图)
利用MVCC提供的快照读能力,可以实现在不加锁的情况下,避免读写冲突时的堵塞问题,极大提升并发查询性能,同时也保证了事务的隔离性。这是在并发性能和数据一致性之间做的一次trade-off。
二、MVCC的实际应用
前面提到,MVCC是在RC和RR隔离级别下使用历史版本数据实现事务隔离性的,下面我们来看个具体的案例分析。
首先先创建表 t1:
CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#插入一条数据
INSERT INTO `t1` (