MySQL触发器导致死锁已通过锁表解决

问题描述

一段时间以来,我一直在与MysqL死锁问题作斗争。我们有很多记录数据的表,然后有插入后触发器,这些触发器每分钟提取一次统计信息/摘要数据,并保存到另一个摘要表中。 显然,这将导致这些插入中的多个影响摘要表中的同一行。但是,由于没有什么等待插入结果继续进行,因此不会造成死锁。插入是分批完成的-每隔几毫秒使用批处理插入。它们可以同时从不同的应用程序完成。 由于这些批处理插入语句从来都不是较大事务的一部分,因此我不太了解为什么会导致死锁。如果有人能解释为什么会发生这种情况,将不胜感激!从错误日志中,我仅看到带有以下内容的行:

RECORD LOCKS space id 118597 page no 67 n bits 80 index PRIMARY of table `logschema`.`table_summary_stats` /* Partition `p_2020_11_02` */ trx id 7600352476 lock_mode X locks rec but not gap
Record lock,heap no 11 PHYSICAL RECORD: n_fields 13; compact format; info bits 0

现在,似乎我终于设法通过在执行批处理插入之前使用“ lock table”语句手动执行MysqL表锁定来摆脱死锁。 我知道在innodb表上执行表级锁定非常不切实际,但是自从我添加了此表锁以来,我还没有看到死锁的发生。

表级锁将解决这种死锁问题是否有意义?它是解决此类问题的一种可接受的方法,还是在使用innodb表时不惜一切代价避免使用表锁?

编辑:摘要表如下所示:

 CREATE TABLE `table_summary_stats` (
  `id` bigint DEFAULT NULL,`DateAndTime` datetime NOT NULL,`address` varchar(45) CHaraCTER SET utf8 COLLATE utf8_general_ci NOT NULL,`group` varchar(255) CHaraCTER SET utf8 COLLATE utf8_general_ci NOT NULL,`result` varchar(255) CHaraCTER SET utf8 COLLATE utf8_general_ci NOT NULL,`count` int DEFAULT NULL,PRIMARY KEY (`DateAndTime`,`group`,`result`,`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY RANGE (to_days(`DateAndTime`))
(PARTITION p_2020_10_26 VALUES LESS THAN (738090) ENGINE = InnoDB,PARTITION p_2020_11_10 VALUES LESS THAN (738105) ENGINE = InnoDB,PARTITION overflow VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */;

触发器将执行此操作:

    INSERT INTO table_summary_stats
SET 
    DateAndTime = date_format(from_unixtime(NEW.appEpochMilli/1000),'%Y-%m-%d %H:%i:00'),address = NEW.address,group = NEW.group,result = NEW.result,count = 1
on duplicate key
update
    count = count + 1

以下是相关的死锁信息:

------------------------
 LATEST DETECTED DEADLOCK
 ------------------------
 2020-11-02 20:00:53 0x7f0cc032a700
 *** (1) TRANSACTION:
 TRANSACTION 7600352761,ACTIVE 0 sec inserting
 MysqL tables in use 2,locked 2
 LOCK WAIT 4 lock struct(s),heap size 1136,2 row lock(s),undo log entries 3
 MysqL thread id 874850,OS thread handle 139654885635840,query id 3299800570 10.15.0.91 cdrwriter update
    INSERT INTO table_summary_stats
    SET 
        DateAndTime = date_format(from_unixtime(NEW.appEpochMilli/1000),count = 1
    on duplicate key
    update
        count = count + 1
 
 *** (1) HOLDS THE LOCK(S):
 RECORD LOCKS space id 118597 page no 67 n bits 80 index PRIMARY of table `sms_cdr`.`table_summary_stats` /* Partition `p_2020_11_02` */ trx id 7600352761 lock_mode X locks rec but not gap
 Record lock,heap no 10 PHYSICAL RECORD: n_fields 13; compact format; info bits 0
  0: len 5; hex 99a7c53ec0; asc    > ;;
  1: len 4; hex 74657374; asc test;;
  2: len 30; hex 7b0a202022737461747573223a20226572726f72222c0a202022636f6465; asc {   "status": "error","code; (total 76 bytes);
  3: len 11; hex 3933373931303130353131; asc 93791010511;;
  4: len 6; hex 0001c5042df9; asc     - ;;
  5: len 7; hex 01000053520238; asc    SR 8;;
  6: sql NULL;
  7: len 4; hex 80057c22; asc   |";;
  8: len 8; hex 80000000642f4d05; asc     d/M ;;
  9: len 8; hex 8000000000c03473; asc       4s;;
  10: len 8; hex 800000001a7e7aee; asc      ~z ;;
  11: len 8; hex 8000000000f2b5b1; asc         ;;
  12: len 8; hex 800000008060b217; asc      `  ;;
 
 
 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
 RECORD LOCKS space id 118597 page no 67 n bits 80 index PRIMARY of table `sms_cdr`.`table_summary_stats` /* Partition `p_2020_11_02` */ trx id 7600352761 lock_mode X locks rec but not gap waiting
 Record lock,heap no 11 PHYSICAL RECORD: n_fields 13; compact format; info bits 0
  0: len 5; hex 99a7c54000; asc    @ ;;
  1: len 4; hex 74657374; asc test;;
  2: len 30; hex 7b0a202022737461747573223a20226572726f72222c0a202022636f6465; asc {   "status": "error","code; (total 76 bytes);
  3: len 11; hex 3933373931303130353131; asc 93791010511;;
  4: len 6; hex 0001c5042cdc; asc,;;
  5: len 7; hex 02000004ea07ff; asc        ;;
  6: sql NULL;
  7: len 4; hex 8003095b; asc    [;;
  8: len 8; hex 8000000036a3a0bb; asc     6   ;;
  9: len 8; hex 8000000000785507; asc      xU ;;
  10: len 8; hex 800000000e23089a; asc      #  ;;
  11: len 8; hex 80000000008c8e08; asc         ;;
  12: len 8; hex 8000000045cb8c64; asc     E  d;;
 
 
 *** (2) TRANSACTION:
 TRANSACTION 7600352476,undo log entries 75
 MysqL thread id 874775,OS thread handle 139672774735616,query id 3299800787 10.15.0.90 cdrwriter update
    INSERT INTO table_summary_stats
    SET 
        DateAndTime = date_format(from_unixtime(NEW.appEpochMilli/1000),count = 1
    on duplicate key
    update
        count = count + 1
 
 *** (2) HOLDS THE LOCK(S):
 RECORD LOCKS space id 118597 page no 67 n bits 80 index PRIMARY of table `sms_cdr`.`table_summary_stats` /* Partition `p_2020_11_02` */ trx id 7600352476 lock_mode X locks rec but not gap
 Record lock,;;
  5: len 7; hex 02000004ea07ff; asc        ;;
  6: sql NULL;
  7: len 4; hex 8003095b; asc    [;;
  8: len 8; hex 8000000036a3a0bb; asc     6   ;;
  9: len 8; hex 8000000000785507; asc      xU ;;
  10: len 8; hex 800000000e23089a; asc      #  ;;
  11: len 8; hex 80000000008c8e08; asc         ;;
  12: len 8; hex 8000000045cb8c64; asc     E  d;;
 
 
 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
 RECORD LOCKS space id 118597 page no 67 n bits 80 index PRIMARY of table `sms_cdr`.`table_summary_stats` /* Partition `p_2020_11_02` */ trx id 7600352476 lock_mode X locks rec but not gap waiting
 Record lock,"code; (total 76 bytes);
  3: len 11; hex 3933373931303130353131; asc 93791010511;;
  4: len 6; hex 0001c5042df9; asc     - ;;
  5: len 7; hex 01000053520238; asc    SR 8;;
  6: sql NULL;
  7: len 4; hex 80057c22; asc   |";;
  8: len 8; hex 80000000642f4d05; asc     d/M ;;
  9: len 8; hex 8000000000c03473; asc       4s;;
  10: len 8; hex 800000001a7e7aee; asc      ~z ;;
  11: len 8; hex 8000000000f2b5b1; asc         ;;
  12: len 8; hex 800000008060b217; asc      `  ;;
 
 *** WE ROLL BACK TRANSACTION (1)

解决方法

“插入是分批完成的”-按4列PK对每个批次进行排序。这应该消除许多僵局,并将其余僵局变成“锁定等待”。 (也就是说,当发生死锁时,它只需等待其他连接完成即可。)

如果可行,还可以将批次限制为100行。

使用分区键使PRIMARY KEY 开始几乎总是无用的。

(我同意您应该避免使用LOCK TABLES。)

说明

经典的死锁是:

我抓住第1行,你抓住第2行,然后我到达第2行(但无法获得),而你到达第1行(也无法获得)。我们俩都不愿意放弃我们拥有的东西。

因此,一名裁判介入并迫使我们中的一个人在他有空时退还他,让另一个人完成比赛。

对于我(或您)来说,不可能(或不切实际)获取所有需要的行;因此实际上实际上一次抓住了一行。想想正在改变数百万行的巨型UPDATE。在我抓住所有这些行时停止所有操作是不明智的。

这称为“乐观”-处理过程假定它将成功并向前犁。在其他任何连接冲突之前,典型事务将以99.999 ...%的时间完成。

如果我们以相同的“顺序”(例如PRIMARY KEY顺序)抓取行,那么我们中的一个人就可以完成;对方可以简单地等待。如果等待时间只有毫秒,那么延迟是无法察觉的。 (在这里限制批量大小会有所帮助。)

更好?

摆脱触发器,简单地做两个批处理语句(一个对原始批处理INSERT进行操作,可能会更好(即更快,死锁的可能性较小),另一个是 batch Upsert(又名IODKU)汇总表。

无论如何,要捕获交易中的错误并重播整个交易。

有关高速插入的更多讨论:http://mysql.rjweb.org/doc.php/staging_table(尽管不直接适用 ,但您可能会找到一些相关的技巧。)