MySQL Lock--Index intersect导致的死锁

涉及表结构

CREATE TABLE `am_friend_send_link` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `task_id` int(11) NOT NULL COMMENT '任务id',
  `robot` int(11) DEFAULT NULL COMMENT '机器人logicId',
  `wechat_username` varchar(30) DEFAULT NULL COMMENT '用户微信id',
  `has_send` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否发送链接 0未发送/1已发送',
  `has_join` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已进群 0未加入/1已加入',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'create_time',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update_time',
  `send_type` tinyint(1) unsigned DEFAULT '0' COMMENT '发送类型 0:二维码 1:短链',
  PRIMARY KEY (`id`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_task_id` (`task_id`),
  KEY `idx_wechat_username` (`wechat_username`)
) ENGINE=InnoDB AUTO_INCREMENT=1137187 DEFAULT CHARSET=utf8mb4 COMMENT='加粉号-发送链接统计'

涉及SQL

update um.am_friend_send_link 
set has_join = true 
where task_id in (154) 
and wechat_username = 'user0002';

update um.am_friend_send_link 
set has_join = true 
where task_id in (154) 
and wechat_username = 'user0001';

三种执行计划(可能)

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: am_friend_send_link
         type: index_merge
possible_keys: idx_task_id,idx_wechat_username
          key: idx_wechat_username,idx_task_id
      key_len: 123,4
          ref: NULL
         rows: 1
        Extra: Using intersect(idx_wechat_username,idx_task_id); Using where; Using temporary
1 row in set (0.00 sec)


*************************** 1. row ***************************
           id: 1
  select_type: UPDATE
        table: am_friend_send_link
   partitions: NULL
         type: range
possible_keys: idx_task_id,idx_wechat_username
          key: idx_wechat_username
      key_len: 123
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set (0.00 sec)


*************************** 1. row ***************************
           id: 1
  select_type: UPDATE
        table: am_friend_send_link
   partitions: NULL
         type: range
possible_keys: idx_task_id,idx_wechat_username
          key: idx_task_id
      key_len: 123
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set (0.00 sec)

死锁信息

LATEST DETECTED DEADLOCK
------------------------
2020-06-15 23:00:28 7efcc9d29700
*** (1) TRANSACTION:
TRANSACTION 206352932923, ACTIVE 0.005 sec fetching rows
mysql tables in use 3, locked 3
LOCK WAIT 122 lock struct(s), heap size 30248, 4 row lock(s)
LOCK BLOCKING MySQL thread id: 746657886 block 696343511
MySQL thread id 696343511, OS thread handle 0x7efc98463700, query id 287972470324 172.16.1.176 um Searching rows for update
update um.am_friend_send_link set has_join = true where task_id in (154) and wechat_username = 'user0002'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 11663 page no 11196 n bits 272 index `PRIMARY` of table `um`.`am_friend_send_link` trx id 206352932923 lock_mode X locks rec but not gap waiting
Record lock, heap no 169 PHYSICAL RECORD: n_fields 11; compact format; info bits 0
 0: len 4; hex 0011397e; asc   9~;;   ## id=1128830
 1: len 6; hex 00300b951d5d; asc  0   ];;
 2: len 7; hex aa000100390110; asc     9  ;;
 3: len 4; hex 8000009a; asc     ;;		## task_id=154
 4: len 4; hex 8002bfcd; asc     ;;
 5: len 11; hex 6861696c692d7a68616e67; asc user0001;;
 6: len 1; hex 80; asc  ;;
 7: len 1; hex 80; asc  ;;
 8: len 5; hex 99a69f6e92; asc    n ;;
 9: len 5; hex 99a69f6e92; asc    n ;;
 10: len 1; hex 00; asc  ;;


*** (2) TRANSACTION:
TRANSACTION 206352932938, ACTIVE 0.002 sec fetching rows, thread declared inside InnoDB 3894
mysql tables in use 3, locked 3
118 lock struct(s), heap size 30248, 3 row lock(s)
MySQL thread id 746657886, OS thread handle 0x7efcc9d29700, query id 287972470345 172.16.1.176 um Searching rows for update
update um.am_friend_send_link set has_join = true where task_id in (154) and wechat_username = 'user0001'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 11663 page no 11196 n bits 272 index `PRIMARY` of table `um`.`am_friend_send_link` trx id 206352932938 lock_mode X locks rec but not gap
Record lock, heap no 169 PHYSICAL RECORD: n_fields 11; compact format; info bits 0
 0: len 4; hex 0011397e; asc   9~;;		id=1128830
 1: len 6; hex 00300b951d5d; asc  0   ];;
 2: len 7; hex aa000100390110; asc     9  ;;
 3: len 4; hex 8000009a; asc     ;;		task_id=154
 4: len 4; hex 8002bfcd; asc     ;;
 5: len 11; hex 6861696c692d7a68616e67; asc user0001;; ## wechat_username='user0001'
 6: len 1; hex 80; asc  ;;
 7: len 1; hex 80; asc  ;;
 8: len 5; hex 99a69f6e92; asc    n ;;
 9: len 5; hex 99a69f6e92; asc    n ;;
 10: len 1; hex 00; asc  ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 11663 page no 9224 n bits 552 index `idx_task_id` of table `um`.`am_friend_send_link` trx id 206352932938 lock_mode X locks rec but not gap waiting
Record lock, heap no 481 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 8000009a; asc     ;;	 task_id=154
 1: len 4; hex 0011397e; asc   9~;;  id=1128830

*** WE ROLL BACK TRANSACTION (2)

加锁顺序(猜测):

1、事务206352932938按照wechat_username='user0001'在索引idx_wechat_username(wechat_username,id)上找到索引记录('user0001',1128830)并加锁成功。
2、事务206352932938按照id=1128830在主键索引Primary Key(id)上找到索引记录(1128830)并加锁成功。
3、事务206352932923按照task_id=154在索引idx_task_id(task_id,id)上找到索引记录(154,1128830)并加锁成功。
4、事务206352932923按照id=1128830在主键索引Primary Key(id)上找到索引记录(1128830)并尝试加锁,该锁资源已被事务206352932938持有,加锁失败并等待锁资源。
5、事务206352932938按照task_id=154在索引idx_task_id(task_id,id)上找到索引记录(154,1128830)并尝试加锁,但该锁资源已被事务206352932923持有,加锁失败并等待锁资源,触发死锁检测机制。
6、死锁检测机制发现死锁环路,回滚事务206352932938。

问题思考

在MySQL官方文档中对intersect操作描述如下:
The Index Merge intersection algorithm performs simultaneous scans on all used indexes and produces the intersection of row sequences that it receives from the merged index scans.

intersect操作需要对所有涉及到的索引进行范围扫描,然后将有序的扫描结果求交集,如本例中对idx_task_id和idx_wechat_username分布按照task_id=154 和wechat_username='user0001' 进行范围扫描,得到的索引记录在id列上是有序的,能快速执行intersect操作,再按照intersect操作后的id值做Primary key lookup操作。intersect操作主要用于减少Primary key lookup次数。

在本例中事务206352932938已经对主键索引加锁成功,即已经读取到id=1128830的数据记录,可以在数据记录上进行task_id=154的条件过来,为何还会对索引idx_task_id上的记录进行范围扫描和加锁?
posted @ 2020-06-17 13:54  TeyGao  阅读(503)  评论(0编辑  收藏  举报