1、事务ID生成的时机
begin语句并不生成事务ID,在执行增删改查操作时,才会生成事务ID。
例如:学生表如下
事务A和事务B分别执行如下语句
A | B |
---|---|
begin | |
begin | |
update 学生 set 年龄 = 10 where 学号 = ‘S001’ | |
select * from 学生 where 学号 = ‘S001’ |
查询的结果为如下,年龄并没有被改变,说明A在select的时候才生成事务ID,因为Innodb的mvcc只读取行的系统版本号小于等于当前事务的系统版本号
2、对mvcc的测试及工作原理分析(基于可重复读)
2、1对mvcc的测试
事务A | 事务B | 事务C |
---|---|---|
begin | begin | beign |
update 课程 set 学分 = 1 where 课程号 = ‘C1’ | ||
update 学生 set 年龄 = 10 where 学号 = ‘S001’ | ||
commit | ||
select * from 学生 where 学号 = ‘S001’ | ||
update 学生 set 年龄 = 30 where 学号 = ‘S001’ | ||
commit | ||
select * from 学生 where 学号 = ‘S001’ |
事务C的第一次查询结果
事务C的第二次查询结果
2、2工作原理分析(基于update操作和select操作)
事务的更新操作会使得旧的记录以快照的形式存在于undo日志中,就相当于一条记录对应的版本链。
每个数据表都有4个隐藏的列,隐藏主键ID、行版本号、行删除版本号、回滚指针(mvcc只涉及后面3个列)。
read view的格式【当前活跃事务的版本号列表(未提交的事务)】【当前最大事务的版本号(包括提交和未提交的)】
活跃事务最小版本号记为minVersion,当前系统最大版本号记为maxVersion,当前记录版本号为recordVersion。满足以下条件时,该记录才会被查询到。
1、recordVersion < minVersion 或者
2、minVersion <= recordVersion <= maxVersion && recordVersion 不属于 活跃事务版本号列表
3、在上述两个条件满足之一的情况下,还需要当前行删除版本号大于maxVersion
事务B执行update操作时的undo日志状态
事务C执行select语句,生成的read view(其它事务版本可见性视图)为【19】【20】(19为事务A的版本号,20为事务B的版本号)。当前活跃事务的最小版本号minVersion为19,当前事务最大版本号为maxVersion为20。查询到的记录版本号recordVersion为20,因为recordVersion<=maxVersion && recordVersion不在活跃事务版本中 && 当前记录行删除版本号为null,因此读取的记录为10。
事务C执行update操作,并且提交时,undo日志的状态
事务C再执行select语句,因为是可重复读,因此read view里保存的依旧是【19】【20】,第一条记录为版本号为19,存在于read view的活跃事务列表中,因此通过回滚指针指向下一条记录。在此处的判断方法和第一次执行select语句相同,因此查询的记录依旧是10。事务后续select延用第一次的select操作的read view,因此有效避免了不可重复读的问题。而rc隔离级别下,每一次的select操作,都会生成一个新的read view,因此第二次读取的记录年龄是30。
2.3 工作原理分析(基于insert)
insert操作只需要在插入的每一行保存当前系统版本号作为行版本号即可,不会产生undo日志。rr级别下会产生幻读。具体原因还不明白。
2.4 工作原理分析(基于delete)
delete操作只需要为删除的每一行保存当前系统版本号作为行删除版本号。不产生undo日志(猜测)