mysql三例死锁场景分析

一、商户批量代发业务

1.表结构(简化):

CREATE TABLE `batch` (

 `batch_id` varchar(32) NOT NULL COMMENT '批次号',

 `cash_id` varchar(32) NOT NULL COMMENT '明细单号',

 `status` int(11) NOT NULL COMMENT '状态:0初始化,1已处理,2成功,3处理中,4失败,5 入库',

 PRIMARY KEY (`cash_id`),

  KEY`idx_batch_id` (`batch_id`),

KEY `idx_status` (`status`)

) ENGINE=InnoDB

2.产生死锁的两个事务:

tx1

update batch set status=0 wherebatch_id=’142’ and status=5

tx2

begin

insert …..

insert into batch (batch_id,cash_id, status)values(‘143’, ‘385’,5)

insert into batch (batch_id,cash_id, status)values(‘143’, ‘386’,5)

insert …..

 

3.死锁日志如下:

*** (1) TRANSACTION:

TRANSACTION 1576868964, ACTIVE 0 sec fetching rows

mysql tables in use 3, locked 3

LOCK WAIT 10 lock struct(s), heap size 2936, 103 rowlock(s), undo log entries 1

MySQL thread id 604347, OS thread handle0x7faf68903700, query id 2125933789 192.168.40.214 pay Searching rows forupdate

update pay_batch_paid

       set status = 0 where batch_id = '142' and status = 5

*** (1) WAITING FOR THIS LOCKTO BE GRANTED:

RECORD LOCKS space id 337 page no 963 n bits 504 index `idx_status` of table `pay`.`pay_batch_paid` trx id1576868964 lock_mode X waiting

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

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

 

*** (2) TRANSACTION:

TRANSACTION 1576868930, ACTIVE 0 sec inserting

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1184, 2 row lock(s), undolog entries 43

MySQL thread id 604176, OS thread handle0x7faf66b0d700, query id 2125933793 192.168.40.44 pay update

insert into batch (batch_id, cash_id, status)values(‘143’,‘386’,5)   

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

RECORD LOCKS space id 337 page no 963 n bits 504 index `idx_status` of table `pay`.`pay_batch_paid` trx id1576868930 lock_mode X locks rec but not gap

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

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

 

*** (2) WAITING FOR THIS LOCKTO BE GRANTED:

RECORD LOCKS space id 337 page no 963 n bits 504 index`idx_status` of table `pay`.`pay_batch_paid` trx id 1576868930 lock_mode X locks gap before rec insert intention waiting

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

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

 

*** WE ROLL BACK TRANSACTION (1)

 4.分析

事务1 update语句where条件status为非聚集索引,需要对status为5的索引值加X、next-key锁。但是加锁是一条一条加的,当加到cash_id为385时,等待。

 

事务2 insert status均为5,先插入cash_id为385记录,会在status索引上对385加X、record锁。接着插入cash_id为386记录,在插入之前会先在插入记录所在的间隙加上一个插入意向gap锁(死锁日志上表现为从它的最近一条385开始加锁)。

 

以上可以粗略解释死锁日志,但是在本地库里模拟不出来死锁。可能解释是错的。

 

5.解决方案:

删除status索引,1是解决死锁的问题;2是status状态就那么几个,根据status查询某个状态效率可能比全表扫效率更低(有mysql数据组织方式决定)。

感觉也不是完美方案,因为根据status查询也很多,而且只查询其中状态为0、5这样的初始化状态数据,这种数据极少。

在删除status索引基础上优化:

1.    根据status字段查询时加上时间段,对时间字段加索引,这种方法时间多长合适不易确定。

2.    因为cash_id主键呈增长趋势,而初始化状态记录都出现在新插入记录中,所以查询时可根据cash_id降序或create_time字段降序查询,并加上limit,且limit值设置的比较小。

 

二、汇金消息接收表死锁

1.表结构

CREATE TABLE `bm_event_record` (

  `event_record_no`varchar(64) NOT NULL COMMENT '事件流水号',

  `node_id` varchar(12)DEFAULT NULL COMMENT '事件树节点好',

  `order_id` varchar(64) NOTNULL COMMENT '业务订单号',

  `event_code` varchar(8) NOTNULL COMMENT '事件码',

  `amount` decimal(16,2) NOTNULL COMMENT '事件发生额',

  `status` int(10) NOT NULLCOMMENT '事件状态',

  `create_time` datetime(3)NOT NULL COMMENT '事件消息发生时间',

  `modify_time` datetime(3)NOT NULL COMMENT '事件消息修改时间',

  PRIMARY KEY(`event_record_no`),

  KEY `idx_order_id_node_id`(`order_id`,`node_id`),

  KEY `idx_create_time`(`create_time`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='事件记录'

2.产生死锁的事务

tx1

insert 如果存在则update

tx2

insert 如果存在则update


3.死锁日志:

*** (1) TRANSACTION:

TRANSACTION 810636678, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

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

MySQL thread id 323742, OS thread handle 0x2b9171a67940, query id180802279 192.168.112.100 root updating

update bm_event_record

         SETorder_id='BKkkk',   event_code='00400001',    amount=123.00,     status=1,     modify_time=1469686196000

        where event_record_no= '1607280000000102200102' and modify_time < 1469686196000

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

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636678 lock_mode X locks rec but notgap waiting

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compact format;info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex8000000000007b00; asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

 

*** (2) TRANSACTION:

TRANSACTION 810636677, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

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

MySQL thread id 323731, OS thread handle 0x2b91717dd940, query id180802280 192.168.112.100 root updating

update bm_event_record SETorder_id='BKkkk',   event_code='00400001',    amount=123,     status=0,      modify_time=1469686195000

        where event_record_no= '1607280000000102200102' and modify_time < 1469686195000

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

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636677 lock mode S locks rec but notgap

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compactformat; info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex8000000000007b00; asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

 

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

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636677 lock_mode X locks rec but notgap waiting

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compactformat; info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex 8000000000007b00;asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

 

*** WE ROLL BACK TRANSACTION (2)

 

4.死锁分析:

insert 记录如果已经存在,对对应记录加S锁。

接下来的update操作,对记录加X锁。

如果两个线程并发对同一条记录执行上述操作,很容易死锁。

 

5.解决方法:

两条语句合并成insert on duplicate update语句。

 

三、测试上一个死锁场景时发现

1.表结构

CREATE TABLE `p1` (

  `id` varchar(32)NOT NULL,

  `name`varchar(20) DEFAULT NULL,

  `age`int(11) DEFAULT NULL,

 PRIMARY KEY (`id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8 

mysql>select * from p1;

+----+------+------+

| id | name |age  |

+----+------+------+

| 1  |jack |   11 |

| 2  |cj11 |   12 |

| 3  |cj11 |   13 |

+----+------+------+

2.死锁事务

tx1

1.insert into p1 select 1,'',11;  (1为主键,且已经存在)===此时加S锁

tx2

2.update p1 set name='jack' where id=1;   等待

tx1

3.update p1set name='' where id=1;  

此时发生死锁,死锁。


3.死锁日志

(1)TRANSACTION:

TRANSACTION811285194, ACTIVE 7 sec starting index read

mysql tablesin use 1, locked 1

LOCK WAIT 2lock struct(s), heap size 360, 1 row lock(s)

MySQL threadid 325369, OS thread handle 0x2b917a4d0940, query id 181925328 localhost rootSearching rows for update

update p1 set name='jack' where id=1

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

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811285194 lock_modeX waiting

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len 1;hex 31; asc 1;;  ====》主键

 1: len 6;hex 00003053a062; asc   0S b;; 

 2: len 7;hex 9b000080360110; asc     6  ;;

 3: len 4;hex 6a61636b; asc jack;;

 4: len 4;hex 8000000b; asc     ;; 

*** (2)TRANSACTION:

TRANSACTION811284986, ACTIVE 86 sec starting index read

mysql tablesin use 1, locked 1

3 lockstruct(s), heap size 360, 2 row lock(s)

MySQL threadid 325368, OS thread handle 0x2b9172142940, query id 181925368 localhost rootSearching rows for update

update p1 setname='' where id=1

*** (2) HOLDSTHE LOCK(S):

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811284986 lock mode S locks rec but not gap

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len1; hex 31; asc 1;;

 1: len6; hex 00003053a062; asc   0S b;;

 2: len7; hex 9b000080360110; asc     6  ;;

 3: len4; hex 6a61636b; asc jack;;

 4: len4; hex 8000000b; asc     ;;

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

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811284986 lock_mode X waiting

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len1; hex 31; asc 1;;

 1: len6; hex 00003053a062; asc   0S b;;

 2: len7; hex 9b000080360110; asc     6  ;;

 3: len4; hex 6a61636b; asc jack;;

 4: len4; hex 8000000b; asc     ;;

*** WE ROLLBACK TRANSACTION (1)  ==>即我的tx2,2.update p1 set name='jack' where id=1;   等待

 

4.死锁分析

 

tx1

1.insert into p1 select 1,'',11;  (1为主键,且已经存在)===此时加S锁

tx2

2.update p1 set name='jack' where id=1;   等待

tx1

3.update p1set name='' where id=1;  

此时因为只有tx1获取到了S锁,此时tx1、tx2均来申请该条记录的X锁,触发防止事务饥饿机制,tx2先来申请X锁的,tx1要等待tx2,从而导致死锁。

 

 

总结:

水平有限,以上分析可能是错的~~

 

正常AB-BA型死锁比较容易分析(包括单条语句锁定记录或索引的顺序不一致导致的死锁),单纯的dml操作导致的死锁比较难分析。

 

这里记录一些分析dml操作死锁的注意点:

1.  insert操作,触发唯一索引重复,对记录加S锁。以及意向gap锁。

参考:http://blog.csdn.net/and1kaney/article/details/51214001

 

2.  delete操作针对记录是否存在、是否有效,不同的锁定方式(其实对where条件的都有效)。避免事务饥饿导致的死锁。

参考:http://hedengcheng.com/?p=844&replytocom=107070

 

3.  mysql锁基础知识:

(1)《mysql技术内幕-innodb存储引擎》

(2)何登成博客 http://hedengcheng.com/?p=771

 

你可能感兴趣的:(问题解决)