[英]Understanding InnoDB deadlock log
我有一个正在执行多个INSERT
查询的事务。 同时,可能会发生一个作业,该作业正在对另一个用户运行的数据库执行一致性检查,该作业通过对数据库发出SELECT
查询来进行复制校验和计算。
问题是有时我的应用程序级事务与执行这些检查的工具中的事务发生死锁:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-05-19 18:25:20 0x7f6eb63e3700
*** (1) TRANSACTION:
TRANSACTION 421588457956552, ACTIVE 0 sec fetching rows
mysql tables in use 2, locked 2
LOCK WAIT 1012 lock struct(s), heap size 123088, 85725 row lock(s)
MySQL thread id 15333390, OS thread handle 140086616594176, query id 2884722462 10.96.7.108 replication_checksum_user Sending data
REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT 'bar', 'bars', '17', 'foo+idx', '688438b1-63b7-4cdd-ba14-5ac2811fce08,688438b1-63b7-4cdd-ba14-5ac2811fce08,6149061644177471', '6f2aecfe-44b8-4b04-8913-5086c5402c02,6f2aecfe-44b8-4b04-8913-5086c5402c02,4356115808993199', COUNT(*) AS cnt, COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(CRC32(CONCAT_WS('#', convert(`field1` using utf8mb4), convert(`field2` using utf8mb4), convert(`field3` using utf8mb4), convert(`field4` using utf8mb4), `field5`, `field6`, `created_time`, `finished_time`, CONCAT(ISNULL(`notification_id`), ISNULL(`attempt_type`), ISNULL(`successful`), ISNULL(`finished_time`)))), 1, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0')))
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 109 page no 16087 n bits 192 index foo_idx of table `bar`.`bars` trx id 421588457956552 lock mode S waiting
Record lock, heap no 120 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
0: len 30; hex 36626361393436342d663735642d343134312d623231652d306130623961; asc 6bca9464-f75d-4141-b21e-0a0b9a; (total 36 bytes);
1: len 16; hex 34373236323438353934373634393231; asc 4726248594764921;;
2: len 6; hex 000028626bdf; asc (bk ;;
3: len 7; hex ba0004c0290d2e; asc ) .;;
4: SQL NULL;
5: SQL NULL;
6: SQL NULL;
7: len 8; hex 8000000000000a07; asc ;;
8: len 5; hex 99ace72654; asc &T;;
9: SQL NULL;
*** (2) TRANSACTION:
TRANSACTION 677538783, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 3
MySQL thread id 15333007, OS thread handle 140113480660736, query id 2884722486 10.96.7.206 app_user Update
INSERT into bars(field1, field2, field3, created_time) VALUES('6bca9464-f75d-4141-b21e-0a0b9a7d06ca', '1656221208545861', 272.97000, CURRENT_TIMESTAMP())
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 109 page no 16087 n bits 192 index foo_idx of table `bar`.`bars` trx id 677538783 lock_mode X locks rec but not gap
Record lock, heap no 120 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
0: len 30; hex 36626361393436342d663735642d343134312d623231652d306130623961; asc 6bca9464-f75d-4141-b21e-0a0b9a; (total 36 bytes);
1: len 16; hex 34373236323438353934373634393231; asc 4726248594764921;;
2: len 6; hex 000028626bdf; asc (bk ;;
3: len 7; hex ba0004c0290d2e; asc ) .;;
4: SQL NULL;
5: SQL NULL;
6: SQL NULL;
7: len 8; hex 8000000000000a07; asc ;;
8: len 5; hex 99ace72654; asc &T;;
9: SQL NULL;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 109 page no 16087 n bits 192 index foo_idx of table `bar`.`bars` trx id 677538783 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 120 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
0: len 30; hex 36626361393436342d663735642d343134312d623231652d306130623961; asc 6bca9464-f75d-4141-b21e-0a0b9a; (total 36 bytes);
1: len 16; hex 34373236323438353934373634393231; asc 4726248594764921;;
2: len 6; hex 000028626bdf; asc (bk ;;
3: len 7; hex ba0004c0290d2e; asc ) .;;
4: SQL NULL;
5: SQL NULL;
6: SQL NULL;
7: len 8; hex 8000000000000a07; asc ;;
8: len 5; hex 99ace72654; asc &T;;
9: SQL NULL;
*** WE ROLL BACK TRANSACTION (2)
我在理解该日志时遇到了困难。 两个事务争夺的共同资源是什么? 是否是正在插入的特定行的索引记录(id = 6bca9464-f75d-4141-b21e-0a0b9a7d06ca)?
根据我的理解和阅读,第一个事务试图获取 S 锁(用于读取),第二个事务为插入持有 X 锁(排他锁)。 但是,第二笔交易在等待第一笔交易持有的是什么? 对我来说,第二个事务似乎正在等待它自己持有的锁 - 这是错误的吗?
另外,这两个事务是否有可能正在等待他们不持有的锁?
有间隙和无间隙部分是什么意思?
解决此类问题的首选方法是什么?
编辑:这是架构
CREATE TABLE `bars` (
`field1` VARCHAR(50) NOT NULL,
`field2` VARCHAR(50) NOT NULL,
`field3` VARCHAR(50) NULL DEFAULT NULL,
`field4` ENUM('WHOLE','PARTIAL') NOT NULL,
`field5` TINYINT(4) NULL DEFAULT NULL,
`field6` DECIMAL(16,2) NOT NULL,
`created_time` DATETIME NOT NULL,
`finished_time` DATETIME NULL DEFAULT NULL,
UNIQUE INDEX `foo_idx` (`field1`, `field2`),
CONSTRAINT `f1_idx` FOREIGN KEY (`field1`) REFERENCES `bays` (`field1`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
然后是两次迁移
ALTER TABLE `bars`
ADD FOREIGN KEY `ff_fk` (`field1`, `field2`)
REFERENCES `fields_references` (`field1`, `field2`);
ALTER TABLE bars
DROP CONSTRAINT ff_fk;
由 pt-table-checksum 运行的 REPLACE 和您的应用程序运行的 INSERT 之间存在争用。
您询问了有关锁定 SELECT 的问题。 一个普通的 SELECT 默认是无锁的,但是如果一个 SELECT 是一个锁定语句的一部分,它就会隐式地变成一个锁定 SELECT。 也就是说,因为 pt-table-checksum 运行REPLACE INTO checksums ... SELECT FROM bars
,它必须在bars
中的某些行上获取 S 锁。
在将数据写入行或变量的语句中使用的任何 SELECT 也是如此。
将应用的事务隔离级别更改为 READ-COMMITTED 有助于避免间隙锁。 但也许 pt-table-checksum 有自己的事务隔离级别,无论如何都会创建间隙锁。
底线是发生了死锁。 您不能全部消除它们,它们是由并发会话完成锁定时的自然结果。 因此,您应该设计代码以捕获异常并根据需要重试。
回复您的评论:
事务 1 不一定持有另一个锁。 以下操作序列可能会导致死锁:
事务 2(您的应用程序)在bars
的单行上获得了一些锁。
事务 1 (pt-table-checksum) 需要锁定一批多行。 至少有一个已被事务 2 锁定,因此 pt-table-checksum 等待。
事务 2 想要锁定它之前没有锁定的另一行,但该行是 pt-table-checksum 尝试锁定的批处理的一部分。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.