2011-01-18 17 views
4

最近、MySQLのテスト(MyISAMを使用)に関するDjangoコミュニティの問題が発生しました。MySQLは間違ったデータを返しますか?

はここでDjangoのチケットです:Djangoのコア開発者のhttp://code.djangoproject.com/ticket/14661

一つは、このテストを思い付いたと私たちの多くは、それを再現することができました。私たちがここまで走っているものは誰でも推測できますか?それは単に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>)を省略すると、可能であれば私たちのシステムでテストできるように、コピー/貼り付けが簡単になります。 –

+0

更新しました、ごめんなさい。 –

+0

どの部分が間違っていますか?最後のクエリだけですか?私は参照してください..同じクエリが2回目の実行で1レコードを失う – RichardTheKiwi

答えて

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つの部分を探します。最初の部分が真の場合、フィールドがヌルかどうかにかかわらず、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ので、バグがマスクされている。この計画

​​

を生成します)私たちは既に#######と記された行で削除しました

+0

あなたの仕事のおかげで素晴らしい説明、 –

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) 

また、フィールドがNOT NULLとマークされているため何も行わないWHEREからAND testapp_tag.id IS NOT NULLを削除すると、問題が解決されるようです。

+0

私はそのOR節を取得しませんか? – RichardTheKiwi

+0

'SHOW WARNINGS'の2つのExplain出力を比較します... –

+0

私は、両方と同じプラン(警告表示なし)で同じ警告を受け取ります。奇妙な! – RichardTheKiwi