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(在5.1.59上验证过)还是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
设置事务隔离级别为读已提交; 设置自动提交为0; 开始事务; 从proc_warnings pw表中删除数据 内连接day_position dp表 ON dp.transaction_id = pw.transaction_id 内连接ivehicle_days vd表 ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;事务2
设置事务隔离级别为读已提交; 设置自动提交为0; 开始事务; 从proc_warnings pw表中删除数据 内连接day_position dp表 ON dp.transaction_id = pw.transaction_id 内连接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' |
正如我所看到的,这两个查询都想要对主键为53的行进行排他性的X锁定。然而,它们都不得删除proc_warnings表中的行。我只是不明白为什么索引被锁定。此外,当proc_warnings表为空或day_position表包含较少的行(例如100行)时,索引也不会被锁定。
进一步调查是运行类似SELECT查询的EXPLAIN命令。它显示查询优化器不使用索引来查询proc_warnings表,这就是我能想到的为什么会阻塞整个主键索引的唯一原因。
简化的情况也可以复现该问题,当只有两个表,并且记录只有几条时,但子表在父表的引用列上没有索引。
创建parent表:
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
创建子表
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
设置事务隔离级别为读已提交; 设置自动提交为0; 开始事务; 从child表中删除c 内连接parent表p ON p.id = c.parent_id WHERE p.id = 1;事务2
设置事务隔离级别为读已提交; 设置自动提交为0; 开始事务; 从child表中删除c 内连接parent表p ON p.id = c.parent_id WHERE p.id = 2;
day_positionиЎЁејҖе§ӢиҝҗиЎҢзј“ж…ўпјҢдҪ дёҚеҫ—дёҚе°Ҷи¶…ж—¶йҷҗеҲ¶еўһеҠ еҲ°500з§’ж—¶пјҢйҖҡеёёдјҡеҢ…еҗ«еӨҡе°‘иЎҢж•°жҚ®пјҹ2пјүеҪ“еҸӘжңүзӨәдҫӢж•°жҚ®ж—¶пјҢиҝҗиЎҢжүҖйңҖзҡ„ж—¶й—ҙжҳҜеӨҡд№…пјҹ - JM HicksSELECT" 然后是一个 "DELETE",SELECT"不锁定行",DELETE"如果检查成功",这正是在最后一条评论之前的9小时内发布的存储过程中的情况。如果这是你自己接受的解决方案,为什么不在这里标记一下,让未来的读者更容易意识到这个变通/解决方案呢?如果是别人的答案,你标记了就可以得到2分 :-) 新年快乐。 - JM Hicks