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所以在锁区间外)