InnoDB MVCC
InnoDB 的 MVCC, 其实是通过 undo log 来实现的, 可以理解为是通过在每行记录后面保存两个隐藏的列来实现的, 分别保存了这个行的创建时间, 一个保存的是行的删除时间. 这里存储的并不是实际的时间值, 而是系统版本号(可以理解为事物的 Id), 每开始一个新的事物, 系统版本号就会自动递增, 事物开始时刻的版本号会作为事物 Id.
对应在数据中的表如下(后面两列是隐藏列, 我们通过查询语句并看不到)
SELECT
InnoDB 会根据以下两个条件检查每行记录:
1. InnoDB 只会查找版本早于当前事务版本的数据行(也就是 行 的系统版本号小于或等于事物的系统版本号), 这样可以确保事物读取的行, 要么是在事物开始前已经存在的, 要么是事物自身插入或者修改过的.
2. 行 的删除版本要么未定义, 要么大于当前事务版本号( 这可以确保事物读取到的行, 在事物开始之前未被删除)
只有条件 1, 2 同时满足的记录, 才能返回作为查询结果
DELETE
InnoDB 会为删除的每一行保存当前系统的版本号( 事物ID) 作为删除标识
看下面的具体例子分析: 第二个事物, Id为 2:
start transaction;
select * from yang;
select * from yang;
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 | |
然后接着执行事物 2 中的 (2), 由于 id = 4 的数据的创建时间(事物 Id = 3), 执行当前事物的 Id 为 2, 而 InnoDB 只会查找事物 Id 小于等于当前事务 Id的数据行, 所以 id = 4 的数据行并不会在执行事务 2 中的 (2) 被检索出来, 在事物 2 中的两条 select 语句检索出来的数据如下
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
1 | yang | 1 | undefined |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
假设2
假设在执行这个事物 ID 为 2 的过程中, 刚执行到 (1), 假设事物执行完事物 3 后, 接着有执行了事物 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), 根据 SELECT 检索条件可以知道, 它会检索创建时间( 创建事物的ID) 小于当前事物 ID 的行和删除事物的 ID 大于当前事务的行, 而 Id = 4 的行上面已经说过了, 而 Id = 1 的行由于删除时间 ( 删除事物 Id) 大于当前事务的 ID, 所以事物 2 的 (2) select * from yang 也会把 id = 1 的数据检索出来, 所以, 事物 2 中的两条 select 语句检索出来的数据都如下:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
1 | yang | 1 | 4 |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
UPDATE
InnoDB 执行 UPDATE, 实际上是新插入了一行记录, 并保存其创建时间为当前事物的 ID, 同时保存当前事务 ID 到要 UPDATE 的行的删除时间.
假设3:
假设在执行完事物 2 的 (1) 后又执行, 其他用户执行了事物 3, 4, 这时, 又有一个用户对这张表执行了 UPDATE 操作:
第 5 个事物:
start transaction;
update yang set name='Long' where id=2;
commit;
根据 UPDATE 的更新原则: 会生成新的一行, 并在原来要修改的列的删除时间上添加本事物 ID, 得到表如下:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
1 | yang | 1 | 4 |
2 | long | 1 | 5 |
3 | fei | 1 | undefined |
4 | tian | 3 | undefined |
2 | Long | 5 | undefined |
继续执行事物 2 的(2), 根据 select 语句的检查条件, 得到下表:
id | name | 创建时间(事务ID) | 删除时间(事务ID) |
1 | yang | 1 | 4 |
2 | long | 1 | 5 |
3 | fei | 1 | undefined |
还是和事物 2 中 (1) select 得到相同的结果.