当前位置: 代码迷 >> 综合 >> Mysql MVCC(多版本并发控制)原理解析
  详细解决方案

Mysql MVCC(多版本并发控制)原理解析

热度:68   发布时间:2023-12-03 15:29:20.0

查看表的信息:

可以通过 show table status 此命令看到表的信息:name(表名)、engine(存储引擎)、version(版本,暂不知道是什么版本)row_format(行的格式:和存储引擎有关)、rows(行数)、avg_row_length(平均每条数据的字节数)、data_length(表数据的总字节数)、max_data_length(表数据的最大容量,与存储引擎有关)、index_length(索引长度)、data_free(已分配但是未使用的空间)、auto_increment(下次自增的值)、create_time(表创建时间)、update_time(表最近更新时间)、check_time(使用check table 检查表的时间)、collation(字符集和排序规则 )、checksum(表的实时校验和)create_option(创建表时的其他选项)、comment(表的注释)

多版本并发控制(MVCC)

实现:

在mysql的innodb存储引擎中,事务有四种隔离级别:未提交读(会导致脏读)、提交读(导致不可重复读)、可重复读(解决了不可重复读,但会出现幻读,mysql通过mvcc解决了幻读问题)、序列化(事务串行执行,性能差,一般不推荐),默认的隔离级别是可重复读。

那innodb是如何解决幻读的呢?他会在每条数据的行后面维护两列:插入时间更新时间两列,里面存储的不是时间戳,是系统版本号,每开始一个新的事务,系统版本号都会递增。

insert:

innodb为新增的每一行都将当前的系统版本号维护到插入时间

update:

innodb为更新的每一行都将当前的系统版本号维护到更新时间

delete:

innodb为删除的每一行都将当前的系统版本号维护到更新时间

select:

innodb查询 插入时间<当前事务的版本号 且 (数据更新时间为空 || 更新时间 >当前事务版本号) 

例子

为了更便于理解,建一张表mvcc_test来举例,表有4个字段,id、name为业务字段,create_time、update_time为模拟innodb的插入时间、更新时间两列,模拟时系统版本号以0开始自增:

create table mvcc_test
(id int not nullprimary key,name varchar(20) null,create_time int null,update_time int null
)
comment '多版本控制测试';

先初始化4条数据,插入时获取当前的系统版本号,第一次事务操作时,系统版本号为0,后续新的事务开启时,系统版本号自增,并set到创建时间字段中。

insert into mvcc_test(id, name, create_time, update_time) values (0,'zhangsan',0,null);

对于innodb来说,所有的操作都是事务(包括查询)。

例子1:在id=0的数据已经提交后,此时的系统版本号为0。如果此时有A客户端进行查询,B客户端进行插入id=1的数据。

如果A事务执行优先于B事务,A事务查询的时候系统版本号自增,此时系统版本号为1,B插入的时候系统版本号为2,由于A与B的执行时间不确定,可能A事务结束了B事务插入才结束,也可能A事务还没结束,B事务已经插入完了,所以此时数据可能是这样的:

 select的时候会查询 插入时间<当前事务的版本号 且 数据更新时间为空 || 更新时间 >当前事务版本号 的数据,A事务的版本号是1,上述两种情况只会查询到id=0的数据。

如果B事务执行优先于A事务,B事务插入的时候版本号为1,A事务查询的时候版本号为2,如果B还未插入完成,A事务已经结束了,是查不到B插入的数据的。或者B事务插入完成,A事务才查询结束,如果此时数据是这样的:那这样的话A事务就查询到的数据不正确,也就是出现了幻影行的问题,mysql通过间隙锁来解决来幻读的问题。

 select的时候会查询 插入时间<当前事务的版本号 的数据,A事务的版本号是2,所以只会查询到id=0和id=1的数据。

例子2:在id=0的数据已经提交后,此时的系统版本号为0。如果此时有A客户端进行查询,B客户端进行删除id=0的数据。

如果A事务执行优先于B事务,A事务查询的时候系统版本号自增,此时系统版本号为1,B删除的时候系统版本号为2,此时数据可能是下面这两种:

 select的时候会查询 插入时间<当前事务的版本号 且 数据更新时间为空 || 更新时间 >当前事务版本号)的数据,A事务的版本号是1,所以A事务仍然会查到id=1的数据

如果B事务优先于A事务,B删除的时候系统版本号为1,A查询的时候版本号为2,此时数据可能是下面两种:

此时第二种仍然会查询到id=0的数据,这个是有问题的,所以仅仅是通过mvcc并不能完全解决幻读的问题,mysql通过间隙锁来防止幻影行的插入问题。

间隙锁

修改存储引擎

注:本例中mvcc_test是innodb存储引擎,将会把它改为MyISAM存储引擎。

1 通过sql来修改:mysql会按行将数据从原表复制到一张新的表中,所以可能会执行很长时间。同时会将原表加上读锁。

alter table mvcc_test engine = MyISAM

2 导出和导入:将数据导出到文件,修改文件中的create table的存储引擎,以及表名,等导入成功后将表改名。需要关注表改名期间增量数据的同步问题。

3 创建与查询:创建和被修改表一样的存储结构、修改存储引擎、通过select  insert语句来插入数据。如果数据量比较大的话,可以通过where条件进行批量插入。

create table mvcc_test_myisam like mvcc_test;
alter table mvcc_test_myisam engine = MYISAM;
insert into mvcc_test_myisam select * from mvcc_test;
-- insert into mvcc_test_myisam select * from mvcc_test where id between 1 and 2 ;(数据量大的情况下,可以批量进行插入)

4 有个pt-online-schema-change 的工具,可以在线进行表的变更,具体原理及使用可以参考下面的这篇文档:

https://cloud.tencent.com/developer/news/171416

  相关解决方案