mysql的默认隔离级别是RR(可重复读),网上随便一查都知道RR会导致幻读(同一个事务里面多次查询的结果不一致),可是我自己测试过后发现在RR下并不存在幻读的问题,哪mysql是怎么解决幻读的呢?有两种手段。1,mvcc(多版本控制),2,范围锁
1. mvcc
每次开启事务后都会递增创建一个版本号(version),之后的增删查改都是基于这个版本号进行操作的。
SELECT (version)
- 读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。version >= createVersion and version < deleteVersion
INSERT (createVersion)
- 将当前事务的版本号保存至行的创建版本号。 createVersion = version
UPDATE (createVersion)
- 新插入一行,并以当前事务的版本号作为新行的创建版本号,同时将原记录行的删除版本号设置为当前事务版本号。 新行createVersion = version,旧行deleteVersion = version
DELETE (deleteVersion)
- 将当前事务的版本号保存至行的删除版本号。 deleteVersion = version
例子:
初始数据
表名:test
id | number | createVersion | deleteVersion |
---|---|---|---|
1 | 1 | 1 |
select * from test; //当前version = 2,由于id=1的createVersion=1小于当前version所以可以查出来。
插入一条数据
id | number | createVersion | deleteVersion |
---|---|---|---|
1 | 1 | 1 | |
2 | 3 | 3 |
这时候如果上面的selct事务没结束继续查询的话并不会查询到id=2的数据。由于新插入的数据createVersion = 3 大于查询的version,这样就避免了幻读的情况。
更新一条数据,update test set number = 10 where id = 2
id | number | createVersion | deleteVersion |
---|---|---|---|
1 | 1 | 1 | |
2 | 3 | 3 | 6 |
2 | 10 | 6 |
如果事务在update之前开启(如version=5)那么只能看到createVersion<=5 and deleteVersion > 5 的数据,那么看到的数据就是update前的
2. 间隙锁
mysql的间隙所是基于索引的,对于唯一索引innode会把间隙所降级为行锁,非唯一索引的话就需要用到间隙锁(也叫范围锁)
id | number |
---|---|
1 | 1 |
2 | 3 |
13 | 3 |
23 | 3 |
31 | 11 |
40 | 40 |
事务一:select * from test where number = 3 for update
对于number索引可以分为多个范围
(无穷小,1)(1,3)(3,3)(3,11)(11,无穷大)
这时候锁住的是(3,3)区间,对应的临界记录是(id=1,number=1)(id=31,number=11),对于这范围内的数据都是被锁住的。
事务二:insert into test(id, number) value(5, 3) //是会被阻塞
事务三:insert into test(id, number) value(25, 4) //也是会被阻塞
事务四:insert into test(id, number) value(35, 4) //也是会被阻塞
事务五:insert into test(id, number) value(22, 12) //插入成功 (因为12>11所以在锁区间外)
事务六:insert into test(id, number) value(71, 11) //插入成功 (number值一样,但是id71>31所以在锁区间外)