摘要

通过创建版本号、删除版本号,让每一次增删改操作都可以复制一份快照,而查询操作通过条件过滤,再加上版本过滤,得到对应事务隔离级别的最终数据。

基础概念

Mysql默认的隔离级别是 RR,可重复读。实现原理就是MVCC。下面看看MVCC的原理。

操作示例

建表语句

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for test
-- ----------------------------
DROP TABLE IF EXISTS `test`;
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;

Mysql在创建表的时候,会多创建三列,其中两列是跟 MVCC相关的。

数据行的版本号 (DB_TRX_ID) (为了方便看,就叫create_version)

删除版本号 (DB_ROLL_PT) (为了方便看,就叫 delete_version)

所以,上面的表结构实际上是这样子的。后面的示例中 忽略 未知列

id

value

create_version

delete_version

未知列

查看MYSQL的默认隔离级别

SELECT @@global.tx_isolation, @@tx_isolation;

mysql 开启CDC mysql 开启mvcc_全局事务

@@global.tx_isolation : 全局事务隔离级别 @@tx_isolation : 当前回话的隔离级别

更改MYSQL隔离级别

# 设全局事务为 RU 读未提交的隔离级别

SET @@global.tx_isolation = 0;

SET @@global.tx_isolation = 'READ-UNCOMMITTED';

# 设置全局事务为 RC 读已提交的事务隔离级别

SET @@global.tx_isolation = 1;

SET @@global.tx_isolation = 'READ-COMMITTED';

# 设置全局事务为 RR 可重复读的隔离级别

SET @@global.tx_isolation = 2;

SET @@global.tx_isolation = 'REPEATABLE-READ';

# 设置全局事务为 串行化 隔离级别

SET @@global.tx_isolation = 3;

SET @@global.tx_isolation = 'SERIALIZABLE';

MVCC 插入原理

假设当前全局事务号是 10,插入一条数据。

begin; -- 获取事务id 为10

insert test (id ,value) value (1,1);

commit;

物理存储如下:

id

value

create_version

delete_version

1

1

10

NULL

再插入一条数据,这时候全局事务id是11,

begin; -- 获取事务id 为 11

insert test (id ,value) value (5,5);

commit;

物理存储如下:

id

value

create_version

delete_version

1

1

10

NULL

5

5

11

NULL

上面是插入的两条数据在mysql物理文件中存储的物理数据。这时候还只有create_version,还没有delete_version

MVCC 删除原理

删除数据,并没有真正的删除数据,只是在原数据行的delete_version上插入当前的事务版本号。

begin; -- 假设这次全局事务id 是 12

delete from test where id = 5;

commit;

这时候,数据的物理文件如下:

id

value

create_version

delete_version

1

1

10

NULL

5

5

11

12

MVCC 修改原理

修改语句

begin; -- 开启事务,假设全局事务id号 是 13

update test set value = 2 where id = 1;

commit;

数据的物理存储如下:

id

value

create_version

delete_version

1

1

10

13

5

5

11

12

5

5

13

NULL

MVCC 查询原理

这个时候查询

select * from test where id >= 1;

这时候查到的结果是:

mysql 开启CDC mysql 开启mvcc_全局事务_02

查询是怎么做的呢?

此时,数据查询规则如下:

被查找的数据行的创建事务号不超过当前事务的创建事务号

上面这句话,说明查询出来的数据行,事务版本号要小于或者等于当前事务的版本,确保数据是在本事务之前或者在本次事务中创建的。不能是在本次事务之后的事务中创建的数据。

被查找的数据行删除事务号要么为NULL,要么大于当前事务号。

只有这样,才能保证查询的数据是没有被删除的(删除事务号为NULL) 或者 在本次事务之后的事务中被删除的(大于当前事务号)

脏读场景

这种场景只会在 RU(Read uncommitted 未提交读) 隔离级别中出现。

这种场景需要先将 事务的隔离级别设置为 RU(Read uncommitted 未提交读)。

这个也用 不可重复读的场景例子,跟不可重复读的区别在,B事务执行插入之后,不需要提交事务,A事务就能查询到数据了。

不可重复读场景

这种场景会在 RC(Read committed 已提交读) 隔离级别中出现。

先将全局事务隔离级别设置为 RC级别。

场景:先开启事务A,然后开启事务B,事务B插入数据,之后事务A去查询,这时候RR隔离级别应该查询不到这种条数据,RC隔离级别能查询到这条数据。不可重复读场景能查询到这条数据。

事务A:

begin ; -- 第1步

select * from test ; 第 2步

select * from test ; 第3步

commit; 第4步

先开启A事务,执行第1步,第2步,查询到的结果如下:

mysql 开启CDC mysql 开启mvcc_全局事务_03

接着开启事务B,执行 第1步、第2步

begin; -- 第1步

insert into test (id ,value) value(7,7); -- 第2步

commit; -- 第3步

这时候再执行事务A的第3步,还是看不到7这条数据。接着执行B的第3步,这时候再执行A的第4步,这时候就能看到7这条数据了。

幻读场景

这种场景会在 RR(REPEATABLE READ 可重复读) 隔离级别中出现。

MYSQL默认的就是这种隔离级别。我们先就这种场景举例如下(这里id建立了唯一索引):

业务需求:表中如果不存在id=6的数据,则插入

事务A执行步骤:

begin; -- 第1步

select * from test where id = 6 ; -- 第2步

-- 业务判断 发现 不存在

insert into test (id ,value) value (6,6); -- 第3步

commit ; -- 第4步

事务B执行步骤:

begin; -- 第1步

insert into test (id ,value) value (6,6); -- 第2步

commit ; -- 第3步

假设A事务执行到了第1步,B事务执行到了第2步,接着执行A事务,这时候A事务第2步发现没有id为6的数据,但是第3步却执行不了,一直挂在锁等待状态。

mysql 开启CDC mysql 开启mvcc_隔离级别_04

这种场景,A事务是看不到id=6的数据,但是A事务也不能执行插入操作,有魔幻的感觉,这就是幻读。

参考文档