• 一个数据库死锁的案例


    昨天一个项目的生产环境出现了数据库死锁问题,导致死锁的订单号已经提交给第三方,但是由于出错回滚到该订单号未记录的状态,结果后续的单子使用的单号仍以该单号开始,这在第三方看来不是新单,而是旧单重复调用接口,就报权限错误;即后续所有的新单都卡在这里。这是一个很严重的bug,自增单号的逻辑显然有问题,但是这里看一下死锁问题。

    查看死锁日志后,发现涉及到死锁的应该是两张表,表1是关联表,一般根据order_id和customer_id查询

    CREATE TABLE `rel_credit_customer_files` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
    `order_id` varchar(32) DEFAULT NULL COMMENT '',
    `customer_id` varchar(32) DEFAULT NULL COMMENT '',
    `file_id` varchar(32) DEFAULT NULL COMMENT '',PRIMARY KEY (`id`),
    KEY `pk_order_customer` (`customer_id`,`order_id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=247 DEFAULT CHARSET=utf8mb4 COMMENT='客户图片关联表啊';

    表2是第三方订单信息表,order_id是本家订单号,vx_order_id是第三方订单号。

    CREATE TABLE `biz_credit_order_info` (
      `order_id` varchar(32) NOT NULL COMMENT ' 订单号',
      `vx_order_id` varchar(50) DEFAULT NULL COMMENT '第三方返回的订单号',
      `withdraw_time` datetime DEFAULT NULL COMMENT '退回时间',PRIMARY KEY (`order_id`) USING BTREE,
      KEY `pk_order_id` (`order_id`) USING BTREE,
      KEY `pk_bank` (`bank`),
      KEY `pk_institution` (`institution_id`),
      KEY `bank_code` (`bank`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='征信订单信息表';

    涉及到的语句很多,查看死锁日志后锁定了关键的语句,事务1

    replace into biz_credit_order_info(order_id,vx_order_id,withdraw_time) values('2020xxx20','vx001011003753200319030116352','2020-09-09 09:09:09');
    
    delete from rel_credit_customer_files where order_id='
    2020xxx20
    ' and customer_id=20;
    
    insert into rel_credit_customer_files(
    customer_id,
    file_id,
    order_id,
    customer_name
    ) values(20,1,'2020xxx20','张三');

    事务2

    delete from rel_credit_customer_files where order_id='
    2020xxx08
    ' and customer_id=8;
    insert into rel_credit_customer_files(
    customer_id,
    file_id,
    order_id,
    customer_name
    ) values(8,2,'2020xxx08','张三');
    update biz_credit_order_info
    vx_order_id = 'vx001011003753187396677275648',
    withdraw_time ='2020-09-09 10:25:29'
    where order_id = '2020xxx08' or vx_order_id = 'vx001011003753187396677275648';

    这两个事务是由两个不同的方法执行的(新增和更新),由于疏忽,语句的执行顺序反了,这就为死锁埋了祸根。

    rel_credit_customer_files表的两条语句只用看删除的语句,忽略其新增语句(新增语句对死锁形成原因没有影响)

    事务1

    事务2
    begin; begin;
    replace into biz_credit_order_info(order_id,vx_order_id,withdraw_time) values('2020xxx20','vx001011003753200319030116352','2020-09-09 09:09:09');
    (Affected rows: 2)
     
     
    delete from rel_credit_customer_files where order_id='
    2020xxx08
    ' and customer_id=8;(Affected rows: 2)
    delete from rel_credit_customer_files where order_id='
    2020xxx20
    ' and customer_id=20;(被阻塞)
     
    (Deadlock found when trying to get lock; try restarting transaction)
    update biz_credit_order_info
    vx_order_id = 'vx001011003753187396677275648',
    withdraw_time ='2020-09-09 10:25:29'
    where order_id = '2020xxx08' or vx_order_id = 'vx001011003753187396677275648';
    Affected rows: 0

    问题在于sql语句写的不严谨导致改语句出现了锁表情况,第一步应该加了next_key锁。第二步的删除语句:本意是根据非唯一索引删除,但是customer_id 为int值与类型不符,发生类型转化,原本走索引变成全表扫描,结果锁所有记录(日志中持有136条记录锁)。第三步的删除操作申请所有记录的锁被阻塞。第四步更新操作有 or操作,正好vx_order_id没有索引,会进行全表扫描(explain它还是会走主键索引),会申请第一步中的记录锁而阻塞,于是发生死锁。发生死锁的删除语句和更新语句虽然不是操作同一条记录,但没有走索引导致可能锁所有行,而两个事务的sql执行顺序也是相反的,导致互相等待发生死锁。以上死锁情况如果是事务2先执行第一步同样会死锁。

    将删除语句的数据类型变更/更新语句的vx_order_id字段加索引/更改语句执行顺序,均可以避免死锁。死锁日志提供的信息有限,还需要根据具体的sql执行以及分析语句的索引使用情况信息,才好找出问题。

  • 相关阅读:
    Bootstrap3基础 thumbnail 圆角类型的div块
    Bootstrap3基础 text-muted/success... 辅助类样式 情景文本颜色
    Bootstrap3基础 text-right/left/center 设置标题右对齐、左对齐、居中
    Bootstrap3基础 table-striped 表格实现隔行换色(浅灰色与白色交替)
    Bootstrap3基础 table-condensed 表格中的单元格紧凑一些
    Bootstrap3基础 table-responsive 响应式表格
    Bootstrap3基础 table-bordered/hover 表格加外边框和鼠标悬停对应行的背景色加深
    Bootstrap3基础 page-header 标题下加分割线
    iOS Swift编程语言
    【强烈推荐】XCODE的插件之王
  • 原文地址:https://www.cnblogs.com/psxfd4/p/13666457.html
Copyright © 2020-2023  润新知