2011-01-18 136 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)什麼都不做,似乎會消除此問題。