【mysql并行批量删除死锁排查】

这篇具有很好参考价值的文章主要介绍了【mysql并行批量删除死锁排查】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

mysql批量删除并插入新数据的场景下,为提高执行效率,使用了多线程并发执行的方式。当然mysql建表时使用了分区(partition)机制,聚焦到我们这次讨论的问题,分区(partition)以及跟案例无关的内容暂且不提。

测试环境并发量不高,简单验证执行ok,预发环境并发量比较高,逻辑验证时,出现了以下错误:


2023-08-03 15:15:17.924 32098 ERROR [] --- [thread-6] druid.sql.Statement                     :149 : {conn-210049, pstmt-220068} execute error. DELETE FROM `tablename_202308`
        WHERE `date` = 20230803 AND `app_code` = 'APPCODE' AND `referer` = ?
         AND `target_type` IN (  1
         , 6
         , 8
         , 9
         , 12
         , 5
         )

        LIMIT 5000;

com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123)
        at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
        at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
        at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370)
        at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3461)

mysql死锁了:Deadlock found,通过查看mysql死锁日志“命令: show engine innodb status;”,得到以下内容:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-08-03 15:15:17 140057117718272
*** (1) TRANSACTION:
TRANSACTION 701477003, ACTIVE 1 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 171 lock struct(s), heap size 24784, 10001 row lock(s), undo log entries 5000
MySQL thread id 1136049, OS thread handle 140057034860288, query id 753710384 172.30.184.173 zykj updating
DELETE FROM `tablename_202308`
        WHERE `date` = 20230803 AND `app_code` = 'APPCODE' AND `referer` = 'happy'
         AND `target_type` IN (  1
         , 6
         , 8
         , 9
         , 12
         , 5
         ) 

        LIMIT 5000

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 234584 page no 6144 n bits 352 index idx_ad_network of table `pre`.`tablename_202308` /* Partition `p14` */ trx id 701477003 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
 0: len 4; hex 0134b293; asc  4  ;;
 1: len 17; hex 3130325f61645f726571756573745f7076; asc 102_ad_request_pv;;
 2: len 1; hex 06; asc  ;;
 3: len 8; hex 176969e363f9f00f; asc  ii c   ;;
 4: len 12; hex 636f6d2e73732e6861707079; asc happy;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 234584 page no 2156 n bits 400 index idx_ad_network of table `pre`.`tablename_202308` /* Partition `p14` */ trx id 701477003 lock_mode X locks rec but not gap waiting
Record lock, heap no 97 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
 0: len 4; hex 0134b293; asc  4  ;;
 1: len 17; hex 3130325f61645f726571756573745f7076; asc 102_ad_request_pv;;
 2: len 1; hex 05; asc  ;;
 3: len 8; hex 176969e3c3f9f003; asc  ii     ;;
 4: len 14; hex 636f6d2e64642e72656164696e67; asc com.dd.reading;;


*** (2) TRANSACTION:
TRANSACTION 701477006, ACTIVE 1 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 64 lock struct(s), heap size 8400, 581 row lock(s), undo log entries 290
MySQL thread id 1136048, OS thread handle 140071716505344, query id 753710360 172.30.184.173 zykj updating
DELETE FROM `tablename_202308`
        WHERE `date` = 20230803 AND `app_code` = 'APPCODE' AND `referer` = 'com.dd.reading'
         AND `target_type` IN (  1
         , 6
         , 8
         , 9
         , 12
         , 5
         ) 

        LIMIT 5000

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 234584 page no 2156 n bits 400 index idx_ad_network of table `pre`.`tablename_202308` /* Partition `p14` */ trx id 701477006 lock_mode X locks rec but not gap
Record lock, heap no 97 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
 0: len 4; hex 0134b293; asc  4  ;;
 1: len 17; hex 3130325f61645f726571756573745f7076; asc 102_ad_request_pv;;
 2: len 1; hex 05; asc  ;;
 3: len 8; hex 176969e3c3f9f003; asc  ii     ;;
 4: len 14; hex 636f6d2e64642e72656164696e67; asc com.dd.reading;;
 
 .... 忽略部分日志
 Record lock, heap no 333 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
 0: len 4; hex 0134b293; asc  4  ;;
 1: len 17; hex 3130325f61645f726571756573745f7076; asc 102_ad_request_pv;;
 2: len 1; hex 05; asc  ;;
 3: len 8; hex 176969e3c3f9f41d; asc  ii     ;;
 4: len 14; hex 636f6d2e64642e72656164696e67; asc com.dd.reading;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 234584 page no 6144 n bits 352 index idx_ad_network of table `pre`.`tablename_202308` /* Partition `p14` */ trx id 701477006 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
 0: len 4; hex 0134b293; asc  4  ;;
 1: len 17; hex 3130325f61645f726571756573745f7076; asc 102_ad_request_pv;;
 2: len 1; hex 06; asc  ;;
 3: len 8; hex 176969e363f9f00f; asc  ii c   ;;
 4: len 12; hex 636f6d2e73732e6861707079; asc happy;;

*** WE ROLL BACK TRANSACTION (2)

表单和索引结构


CREATE TABLE `tablename_202308` (
  `ad_rev_record_id` bigint unsigned NOT NULL COMMENT '主键',
  `app_code` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '',
  `target_id` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '指标id',
  `target_type` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '指标类型',
  `ad_rev_target_id` bigint unsigned NOT NULL COMMENT 'xx主键',
  `this_qid` varchar(200) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '渠道',
  `this_lid` varchar(200) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '',
  `brand` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '品牌',
  `ad_network` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '广告网络:',
  `referer` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '',
  `app_ad_id` varchar(200) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '()',
  `adcdn_ad_id` varchar(200) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '',
  `date` int unsigned NOT NULL DEFAULT '0' COMMENT '日期,格式:yyyyMMdd',
  `value` decimal(14,6) NOT NULL DEFAULT '0.000000' COMMENT '结果',
  `create_by` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建者',
  `update_by` bigint unsigned NOT NULL DEFAULT '0' COMMENT '更新者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`ad_rev_record_id`,`referer`),
  KEY `idx_date_type` (`date`,`target_type`),
  KEY `idx_ad_rev_target_id` (`ad_rev_target_id`),
  KEY `idx_ad_network` (`date`,`target_id`,`ad_network`),
  KEY `idx_qid` (`date`,`target_id`,`this_qid`,`this_lid`),
  KEY `idx_ad` (`date`,`target_id`,`app_ad_id`,`ad_network`,`adcdn_ad_id`),
  KEY `idx_ad_qid` (`date`,`target_id`,`app_ad_id`,`this_qid`,`ad_network`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='精简结果表'
/*!50100 PARTITION BY KEY (referer)
PARTITIONS 20 */

原因分析

从mysql的死锁日志可以看出mysql的删除流程中,会涉及到索引的锁定,索引的删除;
mysql的锁是加在如下位置:

RECORD LOCKS space id 234584 page no 6144 n bits 352 index idx_ad_network of table `pre`.`tablename_202308` /* Partition `p14` */ trx id 701477003 lock_mode X locks rec but not gap

space id 234584 page no 6144 n bits 352 index,
因为我们是事务操作(先删除,后插入),在事务提交前,锁会一直存在。

在我们的case中,两个事务a和b分别持有记录锁A(space id 234584 page no 6144 n bits 352 )和锁B(space id 234584 page no 2156 n bits 400),而又分别去请求锁B(space id 234584 page no 2156 n bits 400)和锁A(space id 234584 page no 6144 n bits 352 ),导致了死锁问题的产生。

重点看下索引:KEY idx_ad_network (date,target_id,ad_network),和删除语句

WHERE `date` = 20230803 AND `app_code` = 'APPCODE' AND `referer` = ?
         AND `target_type` IN (  1
         , 6
         , 8
         , 9
         , 12
         , 5
         )

该索引(idx_ad_network)不能区分出target_type,所以mysql在删除时选择了锁定所有索引页,而多个线程删除时有顺序的差异,最终导致死锁的产生了。

解决方案

在索引中添加target_type字段,那么mysql在删除锁定分页时,可以根据target_type进行区分,不必锁定所有索引页,来避免死锁产生。
如下更改索引后,多线程并发删除不再有死锁的异常(实际场景时app_code固定值,所有没有添加到索引中。)文章来源地址https://www.toymoban.com/news/detail-639157.html

CREATE TABLE `tablename_202308` (
  .... 忽略表字段信息
  PRIMARY KEY (`ad_rev_record_id`,`referer`),
  KEY `idx_ad_rev_target_id` (`date`,`referer`,`target_type`,`ad_rev_target_id`),
  KEY `idx_ad_network` (`date`,`referer`,`target_type`,`target_id`,`ad_network`),
  KEY `idx_qid` (`date`,`referer`,`target_type`,`target_id`,`this_qid`,`this_lid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='精简结果表'
/*!50100 PARTITION BY KEY (referer)
PARTITIONS 30 */

到了这里,关于【mysql并行批量删除死锁排查】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MySQL的index merge(索引合并)导致数据库死锁分析与解决方案

    在DBS-集群列表-更多-连接查询-死锁中,看到9月22日有数据库死锁日志,后排查发现是因为mysql的优化-index merge(索引合并)导致数据库死锁。 index merge(索引合并):该数据库查询优化的一种技术,在mysql 5.1之后进行引入,它可以在多个索引上进行查询,并将结果合并返回。 在

    2024年02月08日
    浏览(37)
  • Mysql 数据库时间与系统时间不一致问题排查

    在我们学习中使用到sysdate这个函数时,发现查出来的日期时间与当前的正确时间不一致,相差8个小时左右,为什么会产生这个问题?又该如何解决? – 在数据库中使用sysdate()函数查询系统时间 select sysdate(); 结果显示: 原因分析1:第一时间想到的是数据库所在的云服务器时

    2024年02月06日
    浏览(45)
  • 【MySQL 】MySQL 创建数据库, MySQL 删除数据库,MySQL 选择数据库

    作者简介: 辭七七,目前大一,正在学习C/C++,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: 七七的闲谈 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖 我们可以在登陆 MySQL 服务后,使用 create 命令创建数据库,语法如下: 以下命令简单的演示了创建数据库的过程,

    2024年02月13日
    浏览(52)
  • MySQL运维实战(3.2) 常见数据库连接失败问题排查

    作者:俊达 我们经常会遇到一些数据库连接失败或访问报错的问题。收集并分析具体的报错信息,可以帮助我们迅速定位问题。 1、客户端到服务端的网络是否畅通,服务端端口是否能连通。 使用ping、telnet等工具探测服务端的端口是否能访问。 如果端口不通,要先排除网络

    2024年01月21日
    浏览(49)
  • MySQL删除数据库

    目录 一、drop命令删除数据库 二、使用mysqladmin删除数据库 三、使用PHP脚本删除数据库 使用普通用户登陆 MySQL 服务器,可能需要特定的权限来创建或者删除 MySQL 数据库,所以这边使用 root 用户登录,root 用户拥有最高权限。 在删除数据库过程中,务必要十分谨慎,因为在执

    2024年02月08日
    浏览(34)
  • MySQL数据库——6、删除数据表

    删除一个数据表,使用 SQL 命令 DROP TABLE 。 DROP TABLE 命令允许从数据库中永久删除指定的数据表及其所有数据。 DROP TABLE table_name;   table_name 是要删除的数据表的名称。 例如,要删除名为 users 的数据表,可以执行以下 SQL 命令: DROP TABLE users;   执行此命令后,名为 users 的数据

    2024年04月11日
    浏览(39)
  • django框架——实现MySQL数据库数据的删除

    在html中的删除按钮中绑定js文件中的事件,带上参数,点击即触发(删除按钮不能是a链接,不然报错) js文件中实现该功能,发起ajax请求到后端视图中对数据库进行操作 路由导入 在视图里面实现删除功能

    2024年02月13日
    浏览(40)
  • MySQL数据库索引的种类、创建、删除

    目录 一:MySQL 索引 1、MySQL 索引介绍 2、 索引的作用  3、索引的副作用 4、 创建索引的原则依据  二、索引的分类和创建 1、 普通索引 (1) 直接创建索引 (2) 修改表方式创建 (3) 创建表的时候指定索引 2、 唯一索引 (1) 直接创建唯一索引 (2) 修改表方式创建

    2024年02月09日
    浏览(37)
  • 【JaveWeb教程】(18) MySQL数据库开发之 MySQL数据库设计-DDL 如何查询、创建、使用、删除数据库数据表 详细代码示例讲解

    下面我们就正式的进入到SQL语句的学习,在学习之前先给大家介绍一下我们要开发一个项目,整个开发流程是什么样的,以及在流程当中哪些环节会涉及到数据库。 2.1 项目开发流程 需求文档: 在我们开发一个项目或者项目当中的某个模块之前,会先会拿到产品经理给我们提

    2024年01月25日
    浏览(68)
  • 如何卸载MySQL数据库以及删除所有有关信息

    目录 前言 第一步 卸载mysql程序  第二步 删除安装目录的mysql项目 第三步 删除MySQL的相关注册表 第四步 删除C盘下的 C:ProgramDataMySQL所有的文件 第五步 删除 C:Documents and SettingsAll UsersApplication DataMySQL 下的文件夹  第六步 重启电脑 尾语 你们是否想要删除MySQL数据库呢?如果

    2024年02月08日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包