【问题标题】:Resolving MySQL gap lock解决 MySQL 间隙锁
【发布时间】:2023-03-25 22:50:01
【问题描述】:

需要帮助解决死锁并了解导致死锁的原因

表:

CREATE TABLE `payments` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `billing_id` int(10) unsigned NOT NULL,
  `foreign_transaction_id` varchar(255) NOT NULL,
  `product_type` enum('invoice') DEFAULT NULL,
  `product_id` int(10) unsigned DEFAULT NULL,
  `subscription_id` int(10) unsigned DEFAULT NULL,
  `credit_id` int(10) unsigned DEFAULT NULL,
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `type` enum('payment') DEFAULT NULL,
  `amount` decimal(8,2) NOT NULL DEFAULT '0.00',
  `fee` decimal(8,2) NOT NULL DEFAULT '0.00',
  `vat` decimal(8,2) DEFAULT '0.00',
  `status` enum('active','deleted') NOT NULL,
  `test` enum('no','yes') NOT NULL DEFAULT 'no',
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_id` (`billing_id`,`foreign_transaction_id`),
  KEY `subscription_id_type` (`subscription_id`,`type`),
  KEY `credit_id_type` (`credit_id`,`type`),
  KEY `product_type` (`product_type`,`product_id`),
  KEY `time` (`time`)
) ENGINE=InnoDB AUTO_INCREMENT=3679531 DEFAULT CHARSET=utf8;

死锁发生后立即来自“innodb status”报告的文本:

LATEST DETECTED DEADLOCK
------------------------
2018-12-17 12:10:38 0x2f8
*** (1) TRANSACTION:
TRANSACTION 153838477815, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 446845346, OS thread handle 974, query id 88919281792 123.123.123.123 root update
INSERT  INTO mydb.`payments`
            SET `billing_id` = '11', `foreign_transaction_id` = '1416436', `product_type` = 'invoice', `product_id` = '232886', `type` = 'payment', `amount` = '9.99', `fee` = '0.454545', `vat` = '0', `status` = 'active', `test` = 'no'
            ON DUPLICATE KEY UPDATE `billing_id` =  '11', `foreign_transaction_id` =  '1416436', `product_type` =  'invoice', `product_id` =  '232886', `type` =  'payment', `amount` =  '9.99', `fee` =  '0.454545', `vat` =  '0', `status` =  'active', `test` =  'no', `id` = LAST_INSERT_ID(`id`)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1898595 page no 25610 n bits 488 index billing_id of table `mydb`.`payments` trx id 153838477815 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 61 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 0000000b; asc     ;;
 1: len 6; hex 313431363434; asc 141644;;
 2: len 4; hex 00239264; asc  # d;;

*** (2) TRANSACTION:
TRANSACTION 153838477576, ACTIVE 1 sec inserting, thread declared inside InnoDB 1
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
MySQL thread id 446846795, OS thread handle 760, query id 88919279740 123.123.123.123 root update
INSERT  INTO mydb.`payments`
            SET `billing_id` = '11', `foreign_transaction_id` = '1416430', `product_type` = 'invoice', `product_id` = '1317214', `type` = 'payment', `amount` = '9.99', `fee` = '0.454545', `vat` = '0', `status` = 'active', `test` = 'no'
            ON DUPLICATE KEY UPDATE `billing_id` =  '11', `foreign_transaction_id` =  '1416430', `product_type` =  'invoice', `product_id` =  '1317214', `type` =  'payment', `amount` =  '9.99', `fee` =  '0.454545', `vat` =  '0', `status` =  'active', `test` =  'no', `id` = LAST_INSERT_ID(`id`)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1898595 page no 25610 n bits 488 index billing_id of table `mydb`.`payments` trx id 153838477576 lock_mode X locks gap before rec
Record lock, heap no 61 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 0000000b; asc     ;;
 1: len 6; hex 313431363434; asc 141644;;
 2: len 4; hex 00239264; asc  # d;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1898595 page no 25610 n bits 488 index billing_id of table `mydb`.`payments` trx id 153838477576 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 61 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 0000000b; asc     ;;
 1: len 6; hex 313431363434; asc 141644;;
 2: len 4; hex 00239264; asc  # d;;

*** WE ROLL BACK TRANSACTION (1)

我猜原因在于复合唯一索引“billing_id”。我想知道 MySQL 在这种情况下是如何一步一步创建锁间隙的。以及应该做些什么来解决它

【问题讨论】:

    标签: mysql innodb deadlock composite-key unique-key


    【解决方案1】:
    • 并非所有死锁都可以避免。
    • 在某些情况下,InnoDB 会对其锁定的内容持悲观态度——这可能比努力避免一些复杂的冲突更有效。
    • 通过在每个 SQL 之后测试错误并准备好ROLLBACK 并重新启动事务来计划死锁。

    查看事务中的所有 SQL 可能会有所帮助。 可能您错过了一个FOR UPDATE,它会将死锁变成“lock_wait_timeout”,它默默地“正常工作”。

    添加

    ON DUPLICATE KEY 
    UPDATE  `billing_id` = '11',
            `foreign_transaction_id` = '1416436',
            `product_type` = 'invoice',
            `product_id` = '232886',
            `type` = 'payment',
            `amount` = '9.99',
            `fee` = '0.454545',
            `vat` = '0',
            `status` = 'active',
            `test` = 'no',
            `id` = LAST_INSERT_ID(`id`)
    

    -->

    ON DUPLICATE KEY 
    UPDATE  `product_type` = VALUES(product_type),
            `product_id` = VALUES(product_id),
            `type` = VALUES(type),
            `amount` = VALUES(amount),
            `fee` = VALUES(fee),
            `vat` = VALUES(vat),
            `status` = VALUES(status),
            `test` = VALUES(test)
    

    也就是说,不要重复唯一/主键;使用VALUES 而不是重复这些值。 (我不知道这是否会有所帮助,但它似乎“正确”。)

    【讨论】:

    • 感谢您的回答。请求中没有其他 sql-s 可与此表一起使用,除了在 INSERT ODKU 之后稍后运行的几个操作(来自上面的日志)的另一个 UPDATE 之外。应用程序的堆栈跟踪显示死锁仅在第一个 INSERT ODKU 之后发生。如果我在应用程序代码中将 INSERT ODKU 分成 3 个不同的查询,您认为对摆脱锁定有什么帮助,例如:if (select from table) { update } else { insert
    • 它以前运行良好,但最近几天 rps 增长了,我开始在这个地方得到锁。看了很多文章还是不知道怎么解决。我只有关于代码中的一些更改的想法,或者尝试使用隔离级别“读取提交”设置为默认
    • @Network_SPY - 不过,查看更多交易可能会有所帮助。
    • 抱歉,回答迟了。此查询不是数据库事务的一部分,它是单一的。即使它与来自该业务逻辑的其他查询一起包装到事务中,死锁仍然会发生