一、商户批量代发业务
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