mysql 制造一个死锁_一个 MySQL 死锁案例分析

对 MySQL 死锁不是特别擅长,业务中遭遇了,就尝试分析一下了。

业务需求,写出了类似这样的 SQL:

1

2INSERT INTO site(third_party_id, data) VALUES(%s, %s)

ON DUPLICATE KEY UPDATE data = %s

其中 site 表主键 id 自增,唯一索引 third_party_id。

SQL 目的很简单,就是从一张三方表中导数据到自己业务的一张表中间。

就这样简单的一句 SQL,没有复杂的事务,只是会出现大量的并发。

并发跑起来之后,很快就会发现程序报出大量死锁,看下死锁记录,可以抓到一条死锁信息大致如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24*** (1) TRANSACTION:

TRANSACTION 4185716323, ACTIVE 0.021 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 5 lock struct(s), heap size 1184, 3 row lock(s)

LOCK BLOCKING MySQL thread id: 252573051 block 235860837

MySQL thread id 235860837, OS thread handle 0x7f6fcc2a4700, query id 1581628064 172.16.7.71 prod update

...

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 794 page no 8 n bits 128 index `PRIMARY` of table ``.`` trx id 4185716323 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 35; compact format; info bits 0

...

*** (2) TRANSACTION:

TRANSACTION 4185716326, ACTIVE 0.014 sec inserting

mysql tables in use 1, locked 1

5 lock struct(s), heap size 1184, 3 row lock(s)

MySQL thread id 235818269, OS thread handle 0x7f6d88fbe700, query id 1581628068 172.16.7.71 prod update

...

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 794 page no 8 n bits 128 index `PRIMARY` of table ``.`` trx id 4185716326 lock_mode X locks gap before rec

Record lock, heap no 2 PHYSICAL RECORD: n_fields 35; compact format; info bits 0

...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 794 page no 8 n bits 128 index `PRIMARY` of table ``.`` trx id 4185716326 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 35; compact format; info bits 0

这里事务 2 由于是 INSERT with ON UPDATE,在 constraint check 中会直接上 X 锁,参见这里。

而同时我们这里主键让其自增,针对 third_party_id 字段来做的操作,于是导致主键索引产生的 gap 锁而不是 record 锁。

接下来,与这里类似,事务2在有了 X gap 锁之后,又申请了个其实没有必要的 insert intention lock,

这一请求排在了事务1的 insert intention lock 请求之后,于是带来了死锁。

这里对于这一导入数据的业务需求,其实一次导入中并不会出现多个相同的 third_party_id,

于是我们直接 SELECT 出来,判断一下再 INSERT 或者 UPDATE 即可,两条语句间也不需要做事务,因为不会中途被改掉。

你可能感兴趣的:(mysql,制造一个死锁)