前几天,线上发生了一次数据库死锁问题,这一问题前前后后排查了比较久的时间,这个过程中自己也对数据库的锁机制有了更深的理解。本文总结了这次死锁排查的全过程,并分析了导致死锁的原因及解决方案。希望给大家提供一个死锁的排查及解决思路。
本文涉及到MySql执行引擎、数据库隔离级别、Innodb锁机制、索引、数据库事务等多领域知识。前车之鉴,后事之师,希望读者们都可以有所收获。
1
现象
某天晚上,同事正在发布,突然线上大量报警,很多是关于数据库死锁的,报警提示信息如下:
{"errorCode":"SYSTEM_ERROR","errorMsg":"nested exception is org.apache.ibatis.exceptions.PersistenceException:
Error updating database. Cause: ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL]
Deadlock found when trying to get lock;
The error occurred while setting parametersn### SQL:
update fund_transfer_stream set gmt_modified=now(),state = ? where fund_transfer_order_no = ? and seller_id = ? and state = 'NEW'
通过报警,我们基本可以定位到发生死锁的数据库以及数据库表。先来介绍下本文案例中涉及到的数据库相关信息。
2
背景情况
我们使用的数据库是Mysql 5.7,引擎是InnoDB,事务隔离级别是READ-COMMITED。
数据库版本查询方法:
select version();
引擎查询方法:
show create table fund_transfer_stream;
建表语句中会显示存储引擎信息,形如:ENGINE=InnoDB
事务隔离级别查询方法:
select @@tx_isolation;
事务隔离级别设置方法(只对当前Session生效):
set session transaction isolation level read committed;
PS:注意,如果数据库是分库的,以上几条SQL语句需要在单库上执行,不要在逻辑库执行。
发生死锁的表结构及索引情况(隐去了部分无关字段和索引):
CREATE TABLE `fund_transfer_stream` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`pay_scene_name` varchar(256) NOT NULL COMMENT '支付场景名称',
`pay_scene_version` varchar(256) DEFAULT NULL COMMENT '支付场景版本',
`identifier` varchar(256) NOT NULL COMMENT '唯一性标识',
`seller_id` varchar(64) NOT NULL COMMENT '卖家Id',
`state` varchar(64) DEFAULT NULL COMMENT '状态', `fund_transfer_order_no` varchar(256)
DEFAULT NULL COMMENT '资金平台返回的状态',
PRIMARY KEY (`id`),UNIQUE KEY `uk_scene_identifier`
(KEY `idx_seller` (`seller_id`),
KEY `idx_seller_transNo` (`seller_id`,`fund_transfer_order_no`(20))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水';
该数据库共有三个索引,1个聚簇索引(主键索引),2个非聚簇索(非主键索引)引。
聚簇索引: