mysql加锁6_MySql-间隙锁和临键锁

说明

mysql的innodb引擎中有七种锁:S锁(Share Lock)共享锁,所有类似于select * from t where id = 1 lock in share mode或者update t set c = c+1 where id = 1的形式,行级锁;

X锁(Exclusive Lock)排他锁,所有select * from t where id = 1 for update的形式。行级锁;

IS锁,意向共享锁,在获取S锁之前加的锁,表级锁;

IX锁,意向排他锁,在获取X锁之前加的锁,表级锁;

Record Lock记录锁,行级锁的真正实现;

Gap Lock间隙锁,锁住一个范围;

Next-key Lock临键锁,Gap Lock+Record Lock的组合;

笔记记载的内容是在版本5.7.24和8.0.13验证通过,以后其他版本的MySql的加锁策略可能会变化。“临键锁”称呼来源于“架构之路”公众号。

加锁策略

加锁锁住的是什么?

索引注意:MySql只有在RR的隔离级别下才有gap lock和next-key lock。原则1:加锁的基本单位是Next-key Lock(左开右闭区间);

原则2:查找到的对象才会加锁;

优化1:索引上的等值查询,给唯一索引加锁的时候,会退化为行锁;

优化2:索引上的等值查询,向右遍历最后一个值不满足等值条件时,next-key lock会退化为间隙锁。

一个不合理的地方:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

案例分析

建表语句:1

2

3

4

5

6

7

8

9

10CREATE TABLE `t` (

`id` int(11) NOT NULL,

`c` int(11) DEFAULT NULL,

`d` int(11) DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `c` (`c`)

) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),

(10,10,10),(15,15,15),(20,20,20),(25,25,25);idcd000

555

101010

151515

202020

252525

案例一:唯一索引等值查询1

2

3

4

5

6

7

8

9--session A

begin;

update t set d = d + 1 where id = 7;

--session B

insert into t values (8,8,8);

--session C

insert t set d = d + 1 where id = 10;

上面开启了三个会话分别执行了三个动作。

分析:原则1:加锁区间为:(5,10];

原则2:id=7的不存在,所以这一行没有锁;

优化1:id=7的行没有锁,不满足优化1;

优化2:id=7,与(5,10]的最后一个条件不相等,退化为gap lock。

综上:所以session A加了gap lock(5,10),session B会阻塞,C会成功。

案例二:非唯一索引等值查询1

2

3

4

5

6

7

8

9--session A

begin;

select id from t where c = 5 lock in share mode;

--session B

update t set d = d + 1 where id = 5;

--session C

insert into t values (7,7,7);

分析:原则1:索引C加了next-key lock(0,5];

原则2:c=5存在,所以索引c=5加锁;

优化1:由于索引覆盖(不访问id索引就可以拿到id的值),虽然C=5索引加锁,但是id=5的索引实际并没有加锁,不满足优化1;

优化2:非唯一索引需要向右继续查找,直到10,所以加上next-key lock(5,10],由于10不满足c=5的条件,所以next-key lock退化为gap lock(5,10)。

所以session B的操作会成功,C会失败。

如果想避开索引优化,可以使用select id from t where c = 5 for update,在加锁时会直接锁住主键索引,或者查询一些其他字段。

案例三:唯一索引范围查询1

2

3

4

5

6

7

8

9

10--session A

begin;

select * from t where id>=10 and id<11 for update;

--session B

insert into t values (8,8,8);

insert into t values (13,13,13);

--session C

update t set d = d + 1 where id = 15;

分析:原则1:next-key lock(5,10]和(10,15];

原则2:查找过程中找到了id为10这一行,加了行锁;

优化1:由于给唯一索引加锁,所以next-key lock(5,10]退化为id=10的行锁;

优化2:不满足等值查询的条件;

所以上面操作的结果是session B的第一个插入操作会成功,第二个插入操作会阻塞;

session C的操作会阻塞。

案例四:非唯一索引范围查询1

2

3

4

5

6

7

8

9--session a

begin;

select * from t where c >= 10 and c < 11 for update;

--session b

insert into t values (8,8,8);

--session c

update t set d = d + 1 where c = 15;

分析:原则1:next-key lock(5,10]和(10,15];

原则2:由于查找到10,所以对10加锁;

优化1:由于不是唯一索引,因此(5,10]不会退化;

优化2:由于不是等值查询,所以(10,15]不会退化;

所以上面的操作是session b阻塞,session c也阻塞。

案例五:唯一索引范围锁bug

对应上面加锁策略中的不合理之处。1

2

3

4

5

6

7

8

9--session a

begin;

select * from t where id > 10 and id <= 15 for update;

--session b

update t set d = d + 1 where id = 20;

--session c

insert into t values (16,16,16);

分析:原则1:范围查询next-key lock(10,15];

原则2:搜索到id=15这一行,所以这一行数据要加锁;

优化1:id=15满足等主键索引值查询,所以next-key lock(10,15]退化为行锁;

优化2:满足等值查询,所以不会退化为gap lock;

由于id是唯一索引,所以理论来说没必要再往后搜索,但是实际上还有一个next-key lock(15,20];

所以session b会锁住,session c也会锁住。

案例六:非唯一索引存在”相等值“

首先插入一条数据:1mysql> insert into t values(30,10,30);

数据库测试有两条c=10的数据了。即:idcd000

555

101010

151515

202020

252525

301030

此时索引C的状态是:

{5-5}-{10-10}-{10-30}-{15,15}-……

执行SQL如下:1

2

3

4

5

6

7

8

9

10--session a

begin;

-- delete的加锁规则和for update是一样的

delete from t where c = 10;

--session b

insert into t values (12,12,12)

--session c

udpate t set d = d + 1 where c = 15;

分析:next-key lock(5,10];

c=10这一行加锁;

非唯一索引,不满足退化条件;

满足条件,向右查询第一个不等的值即c=15,加上next-key lock(10,15],由于15!=10,所以退化为gap lock(10,15);

所以上述操作是session b阻塞,c成功。

案例七:limit加锁1

2

3

4

5

6--session a

begin;

delete from t where c = 10 limit 2;

--session b

insert into t values(12,12,12);

分析:

同案例六,但是在优化2的时候,由于limit 2的存在,所以在遍历到(30,10,30)这一行的时候就停止遍历了,所以session b的结果是成功。

案例八:死锁1

2

3

4

5

6

7

8

9

10

11

12

13--session a

begin;

select id from t where c = 10 lock in share mode;

--session b

update t set d = d + 1 where c = 10;

--session a

insert into t values(8,8,8);

--session b

ERROR 1213(40001);

Deadlock found and tring to get lock ; try restarting transaction;

分析:

session a:next-key lock(5,10];

c = 10加锁;

不满足优化1;

满足优化2,加上next-key lock(10,15]并退化为gap lock(10,15)。

session b:获取gap lock(5,10)成功,再获取c = 10的record lock时进入等待;

session a:

插入c = 8的行被死锁,a b进入互相等待。

这个案例说明:next-key lock是由gap lock和record lock组成的,而且是分两部分获取的。

案例九:排序对锁的影响1

2

3

4

5

6--session a

begin;

select * from t where c >= 15 and c <= 20 order by c desc lock in share mode;

--session b

insert into values (6,6,6);

分析:

由于是order by c desc,所以遍历时第一个是c=20以及最右边的行:next-key(15,20](20,25],其中(20,25]会退化为(20,25)gap lock;

c=15,c=20,c=10加锁,同时id=10,15,20也会枷锁;

由于不是唯一索引,所以不满足优化1;

在索引c上向左遍历,直到c=10才会停止(因为15的左边是10,为了保证10的安全还会对(5,10]加锁),所以next-key lock还包括(5,10]。

案例十:多索引等值查询

首先创建表:1

2

3

4

5

6

7

8

9

10

11

12

13

14CREATE TABLE z (

id INT PRIMARY KEY AUTO_INCREMENT,

b INT,

KEY b(b)

)

ENGINE = InnoDB

DEFAULT CHARSET = utf8;

INSERT INTO z (id, b)

VALUES (1, 2),

(3, 4),

(5, 6),

(7, 8),

(9, 10);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15----session a

BEGIN;

SELECT *

FROM z

WHERE b = 6 FOR UPDATE;

--session b

INSERT INTO z VALUES (2, 4);

INSERT INTO z VALUES (2, 8);

INSERT INTO z VALUES (4, 4);

INSERT INTO z VALUES (4, 8);

INSERT INTO z VALUES (8, 4);

INSERT INTO z VALUES (8, 8);

INSERT INTO z VALUES (0, 4);

INSERT INTO z VALUES (-1, 4);

分析:next-key lock(4,6];

b=6这一行加锁;

不满足;

满足,加gap lock(6,8);

需要牢记的是:这个锁是加在b索引上的,而索引都是有序的,所以我们很轻易得到下图:

b82e0ea8e284cf78c402e93ef2ec1845.png

所以session b的结果是:1

2

3

4

5

6

7

8INSERT INTO z VALUES (2, 4);--success

INSERT INTO z VALUES (2, 8);--blocked

INSERT INTO z VALUES (4, 4);--blocked

INSERT INTO z VALUES (4, 8);--blocked

INSERT INTO z VALUES (8, 4);--blocked

INSERT INTO z VALUES (8, 8);--success

INSERT INTO z VALUES (0, 4);--自增主键如果为0会自动替换为表里最大id的下一位,所以这里实际是(10,4),blocked

INSERT INTO z VALUES (-1, 4);--success

参考

架构师之路:

案例十详解参见其他博客:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值