最近,Django社区出现了一个关于MySQL测试的问题(使用MyISAM)。
这是django票:http://code.djangoproject.com/ticket/14661
其中一位Django核心开发人员提出了这个测试,我们中的许多人都能够复制它。任何人都猜到我们在这里遇到了什么?它只是MySQL中的一个错误还是我错过了什么?
这是测试代码和查询:
DROP TABLE IF EXISTS `testapp_tag`;
CREATE TABLE `testapp_tag` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`name` varchar(10) NOT NULL,
`parent_id` integer
);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t1", NULL);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t2", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t3", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t4", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t5", 3);
SELECT `testapp_tag`.`id`, `testapp_tag`.`name`, `testapp_tag`.`parent_id` FROM `testapp_tag` WHERE NOT ((`testapp_tag`.`id` IN (SELECT U0.`id` FROM `testapp_tag` U0 LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`) WHERE U1.`id` IS NULL) AND `testapp_tag`.`id` IS NOT NULL)) ORDER BY `testapp_tag`.`name` ASC;
SELECT `testapp_tag`.`id`, `testapp_tag`.`name`, `testapp_tag`.`parent_id` FROM `testapp_tag` WHERE NOT ((`testapp_tag`.`id` IN (SELECT U0.`id` FROM `testapp_tag` U0 LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`) WHERE U1.`id` IS NULL) AND `testapp_tag`.`id` IS NOT NULL)) ORDER BY `testapp_tag`.`name` ASC;
这是输出:
mysql> SELECT `testapp_tag`.`id`, `testapp_tag`.`name`, `testapp_tag`.`parent_id` FROM `testapp_tag` WHERE NOT ((`testapp_tag` .`id` IN (SELECT U0.`id` FROM `testapp_tag` U0 LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`) WHERE U1.`id` IS NULL) AND `testapp_tag`.`id` IS NOT NULL)) ORDER BY `testapp_tag`.`name` ASC;
+----+------+-----------+
| id | name | parent_id |
+----+------+-----------+
| 1 | t1 | NULL |
| 3 | t3 | 1 |
| 5 | t5 | 3 |
+----+------+-----------+
3 rows in set (0.00 sec)
mysql> SELECT `testapp_tag`.`id`, `testapp_tag`.`name`, `testapp_tag`.`parent_id` FROM `testapp_tag` WHERE NOT ((`testapp_tag` .`id` IN (SELECT U0.`id` FROM `testapp_tag` U0 LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`) WHERE U1.`id` IS NULL) AND `testapp_tag`.`id` IS NOT NULL)) ORDER BY `testapp_tag`.`name` ASC;
+----+------+-----------+
| id | name | parent_id |
+----+------+-----------+
| 1 | t1 | NULL |
| 3 | t3 | 1 |
+----+------+-----------+
2 rows in set (0.01 sec)
答案 0 :(得分:4)
此表格可靠地运作:
SELECT T.`id`, T.`name`, T.`parent_id`
FROM `testapp_tag` T
WHERE NOT (T.`id` IN (
SELECT U0.`id`
FROM `testapp_tag` U0
LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`)
WHERE U1.`id` IS NULL))
ORDER BY T.`name` ASC;
NOT + IN +额外的过滤器组合似乎抛出了MySQL。这绝对是一个错误。
NOT()中的测试寻找2个部分。如果第一部分为真,则第二部分不可能为真,无论该字段是否为空。这是一个多余的条款,似乎是这个bug的原因。
从ScrumMeister的回答中得到一个提示,我确认该错误是由于针对AUTO_INCREMENT的最后插入的ID进行了某种缓存。
DROP TABLE IF EXISTS `testapp_tag`;
CREATE TABLE `testapp_tag` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`name` varchar(10) NOT NULL,
`parent_id` integer
);
start transaction;
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t1", NULL);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t2", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t3", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t4", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t5", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t6", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t7", 3);
commit;
delete from testapp_tag where id = 6; #######
explain extended
SELECT T.`id`, T.`name`, T.`parent_id`
FROM `testapp_tag` T
WHERE NOT (T.`id` IN (
SELECT U0.`id`
FROM `testapp_tag` U0
LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`)
WHERE U1.`id` IS NULL) AND T.`id` IS NOT NULL)
ORDER BY T.`name` ASC;
show warnings;
制定此计划
select `test`.`t`.`id` AS `id`,`test`.`t`.`name` AS `name`,`test`.`t`.`parent_id` AS `parent_id`
from `test`.`testapp_tag` `T` where ((not(<in_optimizer>(`test`.`t`.`id`,
<exists>(select 1 AS `Not_used` from `test`.`testapp_tag` `U0` left join `test`.`testapp_tag` `U1`
on((`test`.`u1`.`parent_id` = `test`.`u0`.`id`)) where (isnull(`test`.`u1`.`id`)
and (<cache>(`test`.`t`.`id`) = `test`.`u0`.`id`)))))) **or (`test`.`t`.`id` = 7)**)
order by `test`.`t`.`name`
如果插入在t6处停止,并且删除也是t6,则错误被屏蔽,因为添加的子句是或(test.t.id = 6)我们已经在标记为####的行中删除了###
答案 1 :(得分:4)
看起来非常有趣,看起来像是MySql查询优化器中的一个错误。
如果你运行它而不是普通选择:
EXPLAIN EXTENDED SELECT `testapp_tag`.`id`, ....;
SHOW WARNINGS;
EXPLAIN EXTENDED SELECT `testapp_tag`.`id`, ...;
SHOW WARNINGS;
然后,比较EXPLAIN EXTENDED
警告的输出,您可以看到第一次,优化器添加到选择:
or (`test`.`testapp_tag`.`id` = 5)
另请注意,从AND testapp_tag.id IS NOT NULL
中移除WHERE
,NOT NULL
,除了字段标记为{{1}}之外什么都不做,似乎可以解决问题。