【问题标题】:MySQL returning incorrect data?MySQL返回不正确的数据?
【发布时间】:2011-06-11 09:46:42
【问题描述】:

最近 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)

【问题讨论】:

  • 省略提示信息 (mysql>) 使我们更容易复制/粘贴,因此我们可以在可能的情况下在我们的系统上进行测试。
  • 哪一部分错了?只是最后一个查询?我明白了.. 相同的查询在第二次运行时丢失了 1 条记录

标签: sql mysql myisam


【解决方案1】:

这种形式工作可靠:

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 个部分。如果第一部分为真,则第二部分不可能为真,无论该字段是否可以为空。这是一个多余的子句,似乎是导致错误的原因。

从 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 的,则该错误被掩盖,因为添加的子句是 or (test.t.id = 6),我们已经在标记为 #### 的行中删除了它###

【讨论】:

    【解决方案2】:

    看起来很有趣,而且看起来像是 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)
    

    另外,请注意,从 WHERE 中删除 AND testapp_tag.id IS NOT NULL(由于该字段被标记为 NOT NULL)似乎解决了这个问题。

    【讨论】:

    • 我得到了相同的警告和相同的计划(没有显示警告)。好的,重新查询,现在我看到了子句。奇怪!
    猜你喜欢
    • 2017-11-29
    • 2019-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    相关资源
    最近更新 更多