MVCC理论
1. 什么是MVCC
MVCC(Muti-Version Concurrency Control
)是多版本并发控制的方法,一般在数据库关系系统中,实现对数据库并发访问。在MySQL中,MyISAM使用的是表级锁,而InnoDB使用的是行级锁,其中默认的隔离级别Repeat Read
(可重复读)采用的是乐观锁,而乐观锁的实现采用的就是MVCC,其较低系统开销,才早就了InnoDB强大的事务处理能力。
MVCC主要解决读写互相不阻塞问题,每次更新都会产生一个新的版本,读的话可以读历史版本(某个时间点的快照),但MVCC不能解决幻读。
2. MVCC具体实现
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的,分别保存这条行的创建时间和行的删除时间。这里的时间并不是实际的时间值,而是系统版本号(事务的ID)。事务开始时刻的系统版本号会作为事务的ID,每次执行一个新事务,系统版本号就会自动递增。
// 假设版本号从1开始
create table yang(
id int primary key auto_increment,
name varchar(20));
2.1 插入操作(INSERT)
// 第一个事务ID为1
start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
对应在数据中的表如下(后面两列是隐藏列,我们通过查询语句并看不到)
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | undefined |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
2.2 查询操作(SELECT)
InnoDB会根据以下两个条件检查每行记录:
- 行的创建版本号小于<=事务的版本号。
- 行的删除版本号>事务的版本号。
2.3 删除操作(DELETE)
// 第二个事务ID为2
start transaction;
select * from yang; // (1)
select * from yang; // (2)
commit;
- 假设1:在执行这个事务ID为2的过程中,刚执行到(1)时,另一个事务ID为3往这个表里插入了一条数据。
// 第三个事务ID为3
start transaction;
insert into yang values(NULL,'tian');
commit;
这时表中的数据如下:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | undefined |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
4 | tian | 3 | undefined |
然后接着执行事务2中的(2),由于id=4的创建时间(事务ID)为3,执行当前事务的ID为2,而InnoDB只会查找事务ID<=当前事务ID的数据行,所以事务3中的(2)不能获取id=4的数据行,在事务3中的两条Select语句检索的数据如下表所示:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | undefined |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
- 假设2:在执行这个事务ID为2的过程中,刚执行到(1),假设事务执行完事务3后,接着又执行了事务4。
// 第四个事务ID为4
start transaction;
delete from yang where id=1;
commit;
此时数据库中的表如下:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | 4 |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
4 | tian | 3 | undefined |
接着执行事务ID为2的事务(2),由于创建时间(事务ID)< 当前事务ID ,删除时间 > 当前事务ID,因此事务ID为2的事务(2)的检索的数据如下表所示:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | 4 |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
2.4 更新操作(UPDATE)
InnoDB执行Update,实际上是新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要Update的行的删除时间。
- 假设3:在执行完事务2的(1)后又执行,其它用户执行了事务3、4。这时,又有一个用户对这张表执行了Update操作。
// 第5个事务ID为5
start transaction;
update yang set name='Long' where id=2;
commit;
根据Update的更新原则,首先插入一条数据,并将原数据行的删除时间(事务ID)更新为本次事务ID,如下表所示:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | 4 |
2 | long | 1 | 5 |
3 | fei | 1 | undefined |
4 | tian | 3 | undefined |
4 | tian | 3 | undefined |
继续执行事务2的(2),检索的数据如下表所示:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
---|---|---|---|
1 | yang | 1 | 4 |
2 | long | 1 | 5 |
3 | fei | 1 | undefined |
结果和事务2中(1)相同。