前言
我们的应用程序运行多个线程,这些线程DELETE
并行执行查询。这些查询会影响隔离的数据,即,不可能存在并发DELETE
发生在来自不同线程的同一行上。但是,对于每个文档,MySQL DELETE
均对语句使用所谓的“下一个键”锁定,该锁定既锁定匹配键又锁定一些间隙。这会导致死锁,我们发现的唯一解决方案是使用READ COMMITTED
隔离级别。
问题
执行DELETE
具有JOIN
s个大表的复杂语句时会出现问题。在特定情况下,我们有一个带有警告的表,该表只有两行,但是查询需要从两个单独的INNER JOIN
ed表中删除属于某些特定实体的所有警告。查询如下:
DELETE pw
FROM proc_warnings pw
INNER JOIN day_position dp
ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=? AND dp.dirty_data=1
当day_position表足够大时(在我的测试案例中有1448行),那么即使使用READ COMMITTED
隔离模式的任何事务也会阻塞整个 proc_warnings
表。
这个问题始终重现这个样本数据- http://yadi.sk/d/QDuwBtpW1BxB9都在MySQL 5.1(在59年1月5日检查)和MySQL 5.5(在MySQL 5.5.24检查)。
编辑:链接的示例数据还包含查询表的架构和索引,为方便起见,在此处复制:
CREATE TABLE `proc_warnings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`warning` varchar(2048) NOT NULL,
PRIMARY KEY (`id`),
KEY `proc_warnings__transaction` (`transaction_id`)
);
CREATE TABLE `day_position` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_day_id` int(10) unsigned DEFAULT NULL,
`dirty_data` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `day_position__trans` (`transaction_id`),
KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;
CREATE TABLE `ivehicle_days` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d` date DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
KEY `ivehicle_days__d` (`d`)
);
每笔交易的查询如下:
交易1
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
交易2
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;
其中之一总是失败,并显示“超过了锁定等待超时...”错误。将information_schema.innodb_trx
包含以下行:
| trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2' | '3089' | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING' | '2012-12-12 19:58:02' | NULL | NULL | '7' | '3087' | NULL |
information_schema.innodb_locks
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
如我所见,两个查询都希望X
在主键= 53的行上具有排他锁。但是,它们都不必须从proc_warnings
表中删除行。我只是不明白为什么索引被锁定了。此外,当proc_warnings
表为空或day_position
表包含较少的行数(即一百行)时,索引不会被锁定。
进一步的调查是要运行EXPLAIN
类似的SELECT
查询。它表明查询优化器不使用索引来查询proc_warnings
表,这是我可以想象为什么它会阻塞整个主键索引的唯一原因。
简化的情况
当只有两个带有几个记录的表但子表在父表ref列上没有索引时,在更简单的情况下也可以重现问题。
建立parent
表格
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
建立child
表格
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
填写表格
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
在两个并行事务中进行测试:
交易1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 1;
交易2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 2;
两种情况的共同点是MySQL不使用索引。我相信这就是锁定整个表的原因。
我们的解决方案
我们现在可以看到的唯一解决方案是将默认的锁定等待超时从50秒增加到500秒,以使线程完成清理工作。然后保持双手交叉。
任何帮助表示赞赏。
day_position
当表开始运行得太慢以至于您必须将超时限制提高到500秒时,该表通常包含多少行?2)仅拥有样本数据时,要花多长时间?