2011-01-18 2 views
4

Недавно в сообществе Django возникла проблема тестирования MySQL (с использованием MyISAM).MySQL возвращает неверные данные?

Вот Джанго билет: 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>), мы можем скопировать/вставить, чтобы мы могли проверить наши системы, если это возможно. –

+0

Обновлено, извините. –

+0

какая часть неправильная? только последний запрос? Я вижу .. тот же запрос теряет 1 запись на 2-м запуске – 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; 

функция НЕ + IN + дополнительный микс фильтр кажется выкинуть MySQL. Это определенно ошибка.

Тест в NOT() ищет 2 части. Если первая часть верна, вторая не может быть правдой, независимо от того, может ли поле быть нулевым или нет. Это избыточное предложение, которое, по-видимому, является причиной ошибки.

Принимая ответ от ответа ScrumMeister, я подтверждаю, что ошибка связана с каким-то кешированием против последнего вставленного ID в AUTO_INCREMENT.

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), который мы уже удалили в строке, помеченной #######

+0

Отличное объяснение, спасибо за вашу работу. –

4

Кажется очень интересным и выглядит как ошибка в оптимизаторе запросов MySql.

Если запустить это вместо равнинных выбирает:

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

Затем, сравнивая выход из EXPLAIN EXTENDED предупреждений, Вы можете видеть, что первый раз, оптимизатор добавляет к выберите:

Также обратите внимание, что удаление AND testapp_tag.id IS NOT NULL из WHERE, которое ничего не делает, поскольку поле отмечено как NOT NULL, похоже, забирает проблему.

+0

Я не получаю это предложение OR? – RichardTheKiwi

+0

Сравните два объясняющих вывода от 'SHOW WARNINGS' ... –

+0

Я получаю те же предупреждения для обоих и того же плана (без предупреждений на показ). Хорошо переделал запрос, и теперь я вижу предложение. Странный! – RichardTheKiwi

Смежные вопросы