MVVC的技术概述
- MVVC可以避免读写锁并发互斥带来的开销。
- 数据元组的操作版本会记录事务开始的时间,当前版本的事物起始时间戳会影响上一个版本的事物结束时间戳。
MVVC的版本存储方式
- 单一表存储:元组的每个事物版本都会记录在同一张表中,并使用指针按顺序将它们连接起来,用来在不同版本之间查找。
- 主从表存储:主表中始终记录元组的最新版本,从表记录历史版本,最新版本也会指向从表中的历史版本。
- 增量表存储:同样采用主从模式,但是不记录元组的完整数据,仅仅记录修改的字段,数据格式更为紧凑。但是当需要获取元组某个版本的完整数据时,需要遍历多个版本后才能还原数据,性能上会有所损耗。
MVVC表的GC
和JVM的GC一样,MVVC版本数据也需要适当的GC,避免历史版本无节制的增加,哪些被提交的,不活跃的版本可以被释放。
T0 - 初始化数据库插入了两条记录1,2
INSERT INTO `test_isolation`.`user_lock` (`name`, `age`, `age_uidx`, `age_nidx`, `create_version`) VALUES ('a', '1', '1', '1', 't0');
INSERT INTO `test_isolation`.`user_lock` (`name`, `age`, `age_uidx`, `age_nidx`, `create_version`) VALUES ('b', '2', '2', '2', 't0');
T1 - 插入1插入一条记录3,并观察当前事务1的数据视图,可见数据为1,2,3
start transaction;
INSERT INTO `test_isolation`.`user_lock` (`name`, `age`, `age_uidx`, `age_nidx`, `create_version`) VALUES ('c', '3', '3', '3', 't1');
-- 模拟数据视图的查询,事务只能观察到小于等于当前事务号未被删除的记录,以及事务号大于当前事务的已删除的记录
SELECT * FROM test_isolation.user_lock where create_version <= 't1' and (delete_version > 't1' or delete_version is null);
T2 - 事务2插入两条记录4,5,并模拟删除一条已存在的记录2,可见数据为1,4,5
start transaction;
INSERT INTO `test_isolation`.`user_lock` (`name`, `age`, `age_uidx`, `age_nidx`, `create_version`) VALUES ('d', '4', '4', '4', 't2');
INSERT INTO `test_isolation`.`user_lock` (`name`, `age`, `age_uidx`, `age_nidx`, `create_version`) VALUES ('e', '5', '5', '5', 't2');
-- 模拟删除一条记录
UPDATE test_isolation.user_lock SET delete_version = 't2' WHERE id = 2; -- DELETE FROM test_isolation.user_lock SET WHERE id = 2;
-- 模拟数据视图的查询,事务只能观察到小于等于当前事务号未被删除的记录,以及事务号大于当前事务的已删除的记录
SELECT * FROM test_isolation.user_lock where create_version <= 't2' and (delete_version > 't2' or delete_version is null);
commit;
T1 - 再次回到事务1,事务1只能观察到小于等于当前事务号未被删除的记录,以及事务号大于当前事务的已删除的记录
T1没有观察到T2新插入的记录4和5,以及仍然可以看到被删除的记录2
-- -- 模拟数据视图的查询,事务只能观察到小于等于当前事务号未被删除的记录,以及事务号大于当前事务的已删除的记录
SELECT * FROM test_isolation.user_lock where create_version <= 't1' and (delete_version > 't1' or delete_version is null);