index_merge导致死锁案例分析

一、死锁现象描述

1.1 基本环境信息

1、数据库版本以及隔离级别

mysql>select version();
+---------------------+
| version()           |
+---------------------+
| 5.7.26-log          |
+---------------------+
返回行数:[1],耗时:3 ms.

mysql>show variables like '%iso%';
+-------------------------+-----------------+
| Variable_name           | Value           |
+-------------------------+-----------------+
| transaction_isolation   | READ-COMMITTED  |
| tx_isolation            | READ-COMMITTED  |
+-------------------------+-----------------+
返回行数:[2],耗时:7 ms.

1.2 模拟测试准备

1、数据准备

CREATE TABLE `deadlock_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `info_uuid` varchar(50) DEFAULT NULL,
  `view_date` date DEFAULT NULL,
  `view_count` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_view_date` (`view_date`),
  KEY `idx_info_uuid` (`info_uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=30000 DEFAULT CHARSET=utf8

mysql> select * from deadlock_test where info_uuid = 'aa381' and VIEW_DATE = '2020-05-04';
+-------+-----------+------------+------------+
| id    | info_uuid | view_date  | view_count |
+-------+-----------+------------+------------+
| 13908 | aa381     | 2020-05-04 |         77 |
+-------+-----------+------------+------------+
1 row in set (0.02 sec)

mysql> select * from deadlock_test where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04';
+-------+-----------+------------+------------+
| id    | info_uuid | view_date  | view_count |
+-------+-----------+------------+------------+
| 29033 | aa269     | 2020-05-04 |          5 |
+-------+-----------+------------+------------+
1 row in set (0.05 sec)

2、模拟死锁产生场景方案

高并发下,多个客户端并发请求更新deadlock_test表的view_count记录,更新SQL主要如下:

update deadlock_test set view_count=view_count+1 where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04';
update deadlock_test set view_count=view_count+1 where info_uuid = 'aa381' and VIEW_DATE = '2020-05-04';

与update获取锁资源相对应的,我们在进行模拟的时候使用select … for update进行代替进行加锁模拟。

二、模拟死锁故障

2.1 模拟步骤以及现象

1、会话执行情况

Session ASession B
begin;-
select id from deadlock_test force index(idx_info_uuid) where info_uuid = ‘aa269’ and VIEW_DATE = ‘2020-05-04’ for update;begin;
·select id from deadlock_test force index(idx_info_uuid) where info_uuid = ‘aa381’ and VIEW_DATE = ‘2020-05-04’ for update;
select id from deadlock_test force index(idx_view_date) where info_uuid = ‘aa269’ and VIEW_DATE = ‘2020-05-04’ for update; --被hang住-
·select id from deadlock_test force index(idx_view_date) where info_uuid = ‘aa381’ and VIEW_DATE = ‘2020-05-04’ for update; --死锁发生

2、死锁日志查看

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-05-14 22:31:06 0x70000fc2f000
*** (1) TRANSACTION:
TRANSACTION 39033, ACTIVE 30 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 58 lock struct(s), heap size 8400, 4 row lock(s)
MySQL thread id 5145, OS thread handle 123145565908992, query id 183203 localhost root Sending data
select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04' for update
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 291 page no 65 n bits 488 index PRIMARY of table `db1`.`deadlock_test` trx id 39033 lock_mode X locks rec but not gap waiting
Record lock, heap no 324 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80003654; asc   6T;;
 1: len 6; hex 000000009857; asc      W;;
 2: len 7; hex 2a000001ed2e34; asc *    .4;;
 3: len 5; hex 6161333831; asc aa381;;
 4: len 3; hex 8fc8a4; asc    ;;
 5: len 4; hex 8000004d; asc    M;;

*** (2) TRANSACTION:
TRANSACTION 39034, ACTIVE 18 sec fetching rows
mysql tables in use 1, locked 1
58 lock struct(s), heap size 8400, 3 row lock(s)
MySQL thread id 5153, OS thread handle 123145566744576, query id 183204 localhost root Sending data
select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa381' and VIEW_DATE = '2020-05-04' for update
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 291 page no 65 n bits 488 index PRIMARY of table `db1`.`deadlock_test` trx id 39034 lock_mode X locks rec but not gap
Record lock, heap no 324 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80003654; asc   6T;;
 1: len 6; hex 000000009857; asc      W;;
 2: len 7; hex 2a000001ed2e34; asc *    .4;;
 3: len 5; hex 6161333831; asc aa381;;
 4: len 3; hex 8fc8a4; asc    ;;
 5: len 4; hex 8000004d; asc    M;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 291 page no 37 n bits 1272 index idx_view_date of table `db1`.`deadlock_test` trx id 39034 lock_mode X locks rec but not gap waiting
Record lock, heap no 692 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 3; hex 8fc8a4; asc    ;;
 1: len 4; hex 80003654; asc   6T;;

*** WE ROLL BACK TRANSACTION (2)

2.2 锁等待分析

为了分析锁等待的情况,我们把innodb_deadlock_detect参数关闭,MySQL不做死锁检测,这时候我们就可以查看相应的一些锁等待信息

1、Session A 执行通过idx_view_date索引扫描被hang住时刻的锁等待信息

可以看到Session A的第二步已经被SessionB的第一步hang住,此时需要获取的锁资源就是primary key索引上id=13908的行锁。

mysql> SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id\G
*************************** 1. row ***************************
 waiting_trx_id: 39036
 waiting_thread: 5145
  waiting_query: select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04' for update
blocking_trx_id: 39037
blocking_thread: 5153
 blocking_query: NULL
1 row in set, 1 warning (0.00 sec)


mysql> select * from information_schema.innodb_lock_waits ;                                                                                                                                            +-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 39036             | 39184:291:65:324  | 39037           | 39185:291:65:324 |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from information_Schema.innodb_locks;
+------------------+-------------+-----------+-----------+-----------------------+------------+------------+-----------+----------+-----------+
| lock_id          | lock_trx_id | lock_mode | lock_type | lock_table            | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------------+-------------+-----------+-----------+-----------------------+------------+------------+-----------+----------+-----------+
| 39184:291:65:324 | 39036       | X         | RECORD    | `db1`.`deadlock_test` | PRIMARY    |        291 |        65 |      324 | 13908     |
| 39185:291:65:324 | 39037       | X         | RECORD    | `db1`.`deadlock_test` | PRIMARY    |        291 |        65 |      324 | 13908     |
+------------------+-------------+-----------+-----------+-----------------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.01 sec)

2、Session B 执行执行通过idx_view_date索引扫描被hang住时刻的锁等待信息

可以看到,两个事务锁与被锁形成了一个死锁闭环,若MySQL死锁检测打开的情况下,会立刻对其中权重较低的事务进行回滚。Session B通过idx_view_date扫描时需要获取的锁资源为(1034404, 13908),但是该资源已经被Session A持有。

mysql> SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id\G
*************************** 1. row ***************************
 waiting_trx_id: 39037
 waiting_thread: 5153 (rollback)
  waiting_query: select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa381' and VIEW_DATE = '2020-05-04' for update
blocking_trx_id: 39036
blocking_thread: 
 blocking_query: select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04' for update
*************************** 2. row ***************************
 waiting_trx_id: 39036
 waiting_thread: 5145
  waiting_query: select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa269' and VIEW_DATE = '2020-05-04' for update
blocking_trx_id: 39037
blocking_thread: 5153
 blocking_query: select id from deadlock_test force index(idx_view_date) where info_uuid = 'aa381' and VIEW_DATE = '2020-05-04' for update
2 rows in set, 1 warning (0.00 sec)

mysql> select * from information_schema.innodb_lock_waits ;                                                                                                                                            +-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 39037             | 39037:291:37:692  | 39036           | 39036:291:37:692 |
| 39036             | 39036:291:65:324  | 39037           | 39037:291:65:324 |
+-------------------+-------------------+-----------------+------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> select * from information_Schema.innodb_locks;
+------------------+-------------+-----------+-----------+-----------------------+---------------+------------+-----------+----------+----------------+
| lock_id          | lock_trx_id | lock_mode | lock_type | lock_table            | lock_index    | lock_space | lock_page | lock_rec | lock_data      |
+------------------+-------------+-----------+-----------+-----------------------+---------------+------------+-----------+----------+----------------+
| 39037:291:37:692 | 39037       | X         | RECORD    | `db1`.`deadlock_test` | idx_view_date |        291 |        37 |      692 | 1034404, 13908 |
| 39036:291:37:692 | 39036       | X         | RECORD    | `db1`.`deadlock_test` | idx_view_date |        291 |        37 |      692 | 1034404, 13908 |
| 39036:291:65:324 | 39036       | X         | RECORD    | `db1`.`deadlock_test` | PRIMARY       |        291 |        65 |      324 | 13908          |
| 39037:291:65:324 | 39037       | X         | RECORD    | `db1`.`deadlock_test` | PRIMARY       |        291 |        65 |      324 | 13908          |
+------------------+-------------+-----------+-----------+-----------------------+---------------+------------+-----------+----------+----------------+
4 rows in set, 1 warning (0.00 sec)

2.3 整体分析与处理

1、会话加锁步骤分析

Session A:(5145)

  1. select id from deadlock_test force index(idx_info_uuid) where info_uuid = ‘aa269’ and VIEW_DATE = ‘2020-05-04’ for update; 锁资源:idx_info_uuid索引的(idx_info_uuid=‘aa269’,id=29033)获取成功,primary索引的(id=29033)获取成功

  2. select id from deadlock_test force index(idx_view_date) where info_uuid = ‘aa269’ and VIEW_DATE = ‘2020-05-04’ for update; 锁资源:idx_view_date索引的(view_date=‘2020-05-04’,id=13908)获取成功,primary索引的(id=13908)获取 被Session B的2)被hang住 --view_date索引从头到尾扫描,扫描到该记录时被hang住。

Session B:(5153) – rollback

  1. select id from deadlock_test force index(idx_info_uuid) where info_uuid = ‘aa381’ and VIEW_DATE = ‘2020-05-04’ for update; 锁资源:idx_info_uuid索引的(idx_info_uuid=‘aa381’,id=13908)获取成功,primary索引的(id=13908)获取成功

  2. select id from deadlock_test force index(idx_view_date) where info_uuid = ‘aa381’ and VIEW_DATE = ‘2020-05-04’ for update; 锁资源:扫描idx_view_date索引的(view_date=‘2020-05-04’,id=13908)获取被SessionA的3)hang住 --view_date索引从头到尾扫描,扫描到该记录时被hang住

2、处理方法

基于以上的分析,我们可以推断MySQL在使用index_merge进行扫描加锁的期间,一定程度上会扩大锁的范围。为了避免MySQL使用index_merge,我们可以将其调整为复合索引来进行优化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值