生产环境一个定时调度执行报错,错误日志提示有死锁,mysql版本5.7,本地测试8.0版本不会有问题
了解到该任务采用的是mapreduce模式,存在并行执行的可能。于是在本地测试了一下,果然存在死锁。
测试数据准备
CREATE TABLE `cf_finance_mapping_set_check_model` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`mapping_set_name` varchar(128) DEFAULT NULL COMMENT '静态映射名称',
`mapping_set_value` varchar(512) DEFAULT NULL COMMENT '静态映射明细',
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
`scope_code` varchar(128) NOT NULL COMMENT '核算范围编码',
`fee_type` varchar(128) NOT NULL COMMENT '费用类型',
`node` varchar(64) NOT NULL COMMENT '核算节点',
`run_period` varchar(32) NOT NULL COMMENT '运行期间',
`account_period` varchar(32) NOT NULL COMMENT '会计期间',
`tenant` varchar(128) NOT NULL COMMENT '租户',
`extend_info` varchar(1024) DEFAULT '' COMMENT '扩展信息',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_unique_mapping_idx` (`mapping_set_name`,`mapping_set_value`(128),`scope_code`(64),`fee_type`(64),`node`,`run_period`,`account_period`)
)
会话1执行:执行成功
会话2执行:执行被阻塞
会话1再执行:发生死锁
原因分析
1,会话1第一次replace into 插入,给唯一键加X(独占)锁。
2,会话2replace into插入,唯一键冲突了,给唯一键加S(共享)锁wait。
3,会话1再次replace into 插入,唯一键冲突,给唯一键加S(共享),这时候S锁已经被会话2占有,会话1需要等待会话2释放S锁,会话2需要等待会话1释放X锁,死锁构成。
解决方案
1,用insert ignore into 替换 replace into,在插入相同唯一键时直接忽略不进行插入。
2,用分布式锁将链路改成串行。
3,replace into 改成先删除,再批量插入。