2011-01-18 146 views
4

Django社区最近出现了一个关于MySQL测试(使用MyISAM)的问题。MySQL返回不正确的数据?

这里的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

省略提示信息(mysql>)使我们更容易复制/粘贴,因此如果可能的话我们可以在我们的系统上进行测试。 – 2011-01-18 22:16:08

+0

更新,对此感到遗憾。 – 2011-01-18 22:20:27

+0

哪部分是错误的?只是最后一个查询?我明白了......同样的查询在第二轮中失去了1条记录 – RichardTheKiwi 2011-01-18 22:22:54

回答

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; 

未+ IN +额外的过滤器组合似乎抛出MySQL。这绝对是一个错误。

NOT()中的测试查找2个部分。如果第一部分是真的,第二部分不可能是真的,不管该字段是否可以为空。这是一个冗余条款,似乎是错误的原因。

从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; 

生成此规划

​​

如果插入停止在t6和删除是T6也,错误被屏蔽,因为添加的子句是或(test.t.id = 6 )我们已经在标记为#######

4

的行中被删除了,看起来很有趣,看起来像MySql查询优化器中的一个bug。

如果你运行这个,而不是简单的选择:

EXPLAIN EXTENDED SELECT `testapp_tag`.`id`, ....; 
SHOW WARNINGS; 
EXPLAIN EXTENDED SELECT `testapp_tag`.`id`, ...; 
SHOW WARNINGS; 

然后,比较从EXPLAIN EXTENDED警告输出,你可以看到,第一次,优化增加了选择:

or (`test`.`testapp_tag`.`id` = 5) 

此外,请注意,从WHERE中删除AND testapp_tag.id IS NOT NULL(该字段未标记为NOT NULL)什么都不做,似乎会消除此问题。