1. 程式人生 > >讓天下沒有難用的資料庫 » MySQL update use index merge(Using intersect) increase chances for deadlock

讓天下沒有難用的資料庫 » MySQL update use index merge(Using intersect) increase chances for deadlock

昨天一同事發現線上系統在併發更新的時候出現了死鎖,通過排查定位於update更新使用了兩個索引導致,死鎖資訊如下:

*** (1) TRANSACTION:
TRANSACTION 29285454235, ACTIVE 0.001 sec fetching rows
mysql tables in use 3, locked 3
LOCK WAIT 6 lock struct(s), heap size 1184, 4 row lock(s)
MySQL thread id 6641616, OS thread handle 0x2b165c4b1700, query id 28190427937 10.103.180.86 test_ebs Searching rows for update
UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4580605
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 132 page no 37122 n bits 352 index `PRIMARY` of table `testdriver`.`test` trx id 29285454235 lock_mode X locks rec but not gap waiting
Record lock, heap no 179 PHYSICAL RECORD: n_fields 8; compact format; info bits 0
*** (2) TRANSACTION:
TRANSACTION 29285454237, ACTIVE 0.001 sec fetching rows, thread declared inside InnoDB 4980
mysql tables in use 3, locked 3
5 lock struct(s), heap size 1184, 3 row lock(s)
MySQL thread id 6639213, OS thread handle 0x2b1694cc2700, query id 28190427939 10.103.180.113 test_ebs Searching rows for update
UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4212859
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 132 page no 37122 n bits 352 index `PRIMARY` of table `testdriver`.`test` trx id 29285454237 lock_mode X locks rec but not gap
Record lock, heap no 179 PHYSICAL RECORD: n_fields 8; compact format;

表結構:
CREATE TABLE `test` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主鍵’,
`test_id` bigint(20) DEFAULT NULL,
`group_id` bigint(20) DEFAULT NULL COMMENT ‘Id,對應test_group.id’,
`gmt_created` datetime DEFAULT NULL COMMENT ‘建立時間’,
`gmt_modified` datetime DEFAULT NULL COMMENT ‘修改時間’,
`is_deleted` tinyint(4) DEFAULT ‘0’ COMMENT ‘刪除。’,
PRIMARY KEY (`id`),
KEY `idx_testid` (`test_id`),
KEY `idx_groupid` (`group_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7429111 ;

SQL執行計劃:
mysql>explain UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4212859
+—-+————-+—————+————-+———————–+———————–+———+—–+——+———
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+—————+————-+———————–+———————–+———+—–+——+———
| 1 | SIMPLE | test | index_merge | idx_testid,idx_groupid | idx_testid,idx_groupid | 9,9 | | 1 | Using intersect(idx_testid,idx_groupid); Using where; Using temporary |
+—-+————-+—————+————-+———————–+———————–+———+—–+——+———

所以第一個事務先根據group_id索引,已經鎖住primary id,然後再根據test_id索引,鎖定primary id;
第二個事務先根據test_id索引,已經鎖住primary id,然後再根據group_id索引,去鎖primary id;
所以這樣併發更新就可能出現死索引。

MySQL官方也已經確認了此bug:https://bugs.mysql.com/bug.php?id=77209

解決方法有兩種:

第一、新增test_id+group_id的組合索引,這樣就可以避免掉index merge;

第二、將優化器的index merge優化關閉;

建議選擇第一種方法來避免此問題的發生。