子查询处理比需要更多的行

Posted

技术标签:

【中文标题】子查询处理比需要更多的行【英文标题】:Subquery processing more rows than necessary 【发布时间】:2016-07-19 12:00:55 【问题描述】:

我正在优化我的查询,发现了一些我无法理解的东西。

我正在使用以下查询来选择一堆类别,并将它们与包含类别旧别名和新别名的表中的别名相结合:

SELECT `c`.`id` AS `category.id`, 
    (SELECT `alias`
    FROM `aliases`
    WHERE category_id = c.id
    AND `old` = 0
    AND `lang_id` = 1
    ORDER BY `id` DESC
    LIMIT 1) AS `category.alias`
FROM (`categories` AS c)
WHERE `c`.`status` =  1 AND `c`.`parent_id` =  '11';

parent_id 只有 2 个类别的值为 11,因此它应该从别名表中查找 2 个类别。

如果我使用EXPLAIN,它仍然说它必须处理 48 行。别名表也包含每个类别的 1 个条目(在这种情况下,它可以更多)。一切都已编入索引,如果我理解正确,它应该立即找到正确的别名。

现在奇怪的事情来了。当我不按条件中的类别比较别名,而是通过查询返回的类别 ID 手动比较别名时,它确实只处理 1 行,正如索引所预期的那样。

所以我将WHERE category_id = c.id 替换为WHERE category_id IN (37, 43) 并且查询变得更快:

我唯一能想到的是,子查询不是在查询结果上运行,而是在一些过滤完成之前运行。欢迎任何形式的解释或帮助!

编辑: 傻瓜,WHERE IN 不起作用,因为它没有做出独特的选择。但问题仍然存在!

创建表架构

CREATE TABLE `aliases` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `lang_id` int(2) unsigned NOT NULL DEFAULT '1',
    `alias` varchar(255) DEFAULT NULL,
    `product_id` int(10) unsigned DEFAULT NULL,
    `category_id` int(10) unsigned DEFAULT NULL,
    `brand_id` int(10) unsigned DEFAULT NULL,
    `page_id` int(10) unsigned DEFAULT NULL,
    `campaign_id` int(10) unsigned DEFAULT NULL,
    `old` tinyint(1) unsigned DEFAULT '0',
    PRIMARY KEY (`id`),
    KEY `product_id` (`product_id`),
    KEY `category_id` (`category_id`),
    KEY `page_id` (`page_id`),
    KEY `alias_product_id` (`product_id`,`alias`),
    KEY `alias_category_id` (`category_id`,`alias`),
    KEY `alias_page_id` (`page_id`,`alias`),
    KEY `alias_brand_id` (`brand_id`,`alias`),
    KEY `alias_product_id_old` (`alias`,`product_id`,`old`),
    KEY `alias_category_id_old` (`alias`,`category_id`,`old`),
    KEY `alias_brand_id_old` (`alias`,`brand_id`,`old`),
    KEY `alias_page_id_old` (`alias`,`page_id`,`old`),
    KEY `lang_brand_old` (`lang_id`,`brand_id`,`old`),
    KEY `id_category_id_lang_id_old` (`lang_id`,`old`,`id`,`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=112392 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

【问题讨论】:

嗯?架构与 SELECT 不匹配——statusparent_id 在哪里? statusparent_id 来自主表 (categories),而不是子查询(注意 c 别名) 【参考方案1】:
SELECT ...
    WHERE x=1 AND y=2
    ORDER BY id DESC
    LIMIT 1

将以多种方式之一执行。

由于您没有向我们展示您拥有的索引 (SHOW CREATE TABLE),我将介绍一些可能的情况...

INDEX(x, y, id) -- 这可以找到该条件的最后一行,因此它不需要查看超过一行。 其他一些索引,或没有索引:从最后一个 id 开始扫描 DESC,检查每一行是否有 x=1 AND y=2,当(如果)找到这样的行时停止。 一些其他索引,或者没有索引:扫描整个表,检查每一行是否有x=1 AND y=2;将它们收集到临时表中;按id排序;送一排。

EXPLAIN的一些线索:

使用 where -- 没啥好说的 使用文件排序——它做了一个排序,显然是针对ORDER BY。 (它可能完全在 RAM 中完成;忽略“文件”。) 使用索引条件(不是“使用索引”)——这表示内部优化,它可以比旧版本更有效地检查 WHERE 子句。

不要相信EXPLAIN 中的“行”。通常它们是相当正确的,但有时它们会相差几个数量级。这是查看“多少工作”在相当快的查询中完成的更好方法:

FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';

通过CREATE TABLE,我可能有关于如何改进索引的建议。

【讨论】:

感谢您提供更多信息。很有意思!我很难理解会话状态查询的结果。 我添加了 CREATE 模式。奇怪的是,在我的本地机器上,它确实使用了更好的基于键的索引。 STATUS的简单解释:“一些大数字=慢;所有小数字=快” 您有多个冗余密钥:如果您有INDEX(a,b,c),则不需要INDEX(a,b)INDEX(a) 是的,在某些情况下“统计数据”并不完全正确,或者数据以一种奇怪的方式倾斜,或者优化器“弄错了”。 5.7 在这方面做了一些改进; 5.8 可能会做出更多改进。同时,EXPLAIN 数字“永远”不会完全正确。

以上是关于子查询处理比需要更多的行的主要内容,如果未能解决你的问题,请参考以下文章

mysql 行子查询

sql 行子查询

mysql 标量子查询和非法子查询

删除作为其他行子字符串的行

MySQL随记 - 子查询

加入两个查询会返回比预期更多的行?