如果 ASC 和 DESC 混合使用,为啥 MySQL 不能为 ORDER BY 使用索引?

Posted

技术标签:

【中文标题】如果 ASC 和 DESC 混合使用,为啥 MySQL 不能为 ORDER BY 使用索引?【英文标题】:Why can't MySQL use an index for ORDER BY if ASC and DESC are mixed?如果 ASC 和 DESC 混合使用,为什么 MySQL 不能为 ORDER BY 使用索引? 【发布时间】:2015-08-10 07:14:47 【问题描述】:

假设我有一个非常简单的表格,如下所示:

CREATE TABLE `t1` (
  `key_part1` INT UNSIGNED NOT NULL,
  `key_part2` INT UNSIGNED NOT NULL,
  `value` TEXT NOT NULL,
  PRIMARY KEY (`key_part1`, `key_part2`)
) ENGINE=InnoDB

使用这个表,我想发出这样的查询:

SELECT *
FROM `t1`
ORDER BY `key_part1` ASC, `key_part2` DESC
LIMIT 1

我曾希望这个查询中的ORDER BY 会被索引所满足。不过根据mysql documentation:

在某些情况下,MySQL 无法使用索引来解析ORDER BY,尽管它仍然使用索引来查找与WHERE 子句匹配的行。这些案例包括:

你把ASCDESC混在一起:

SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;

我尝试了与上述查询类似的查询,正如预期的那样,EXPLAIN 输出表明这样的查询执行文件排序。这对我来说并不完全有意义,因为我可以执行以下操作:

SELECT *
FROM `t1`
WHERE `key_part1` = (
    SELECT `key_part1`
    FROM `t1`
    ORDER BY `key_part1` ASC
    LIMIT 1
)
ORDER BY `key_part2` DESC
LIMIT 1

当我EXPLAIN 这个时,它说子查询和外部查询都不使用文件排序。此外,我尝试了这种结构类似的技巧大表,发现它使我的查询速度提高了 3 个数量级。

我的问题是

    我在此处显示的两个查询是否相同?他们似乎是,但我可能遗漏了一些东西。如果不是,我的表中需要什么样的数据才能使它们给出不同的结果? 是否有原因导致 MySQL 无法自行执行此优化技巧,或者这只是可能的优化案例,但尚未写入 MySQL?

如果重要的话,我使用的是 MySQL 5.6.22。

进一步说明:

“等效”是指“产生相同的结果”。此外,我非常清楚,如果我将 LIMIT 1 更改为 LIMIT 2 或其他内容,查询将不再产生相同的结果。 我对那些情况不感兴趣,只对LIMIT 1的情况感兴趣。

【问题讨论】:

我比较确定MySQL是按升序存储索引的,所以你不能一次双向读取。您可以使用的一个技巧是指定第三列,即 key_part2 的否定(即 -1 * key_part2),在其上创建复合索引,并按两个值升序排序。 doc ref here,大约下降了三分之一。 key_parts 是什么?我问是因为列的语义可以提供替代解决方案。 我必须处理的这个问题的具体情况是,key_part1 是一个只有几个值的枚举,key_part2 是另一个表的外键。 您在第二个查询中有一个 where 子句,而不是第一个。 【参考方案1】:

并不是说 MySQL 缺少优化“技巧”,而是复合索引如何工作的特性。 MySQL 一次只能在一个方向上进行索引扫描,并且必须与索引的排序方式一致(因此它可以进行计算机科学方面的事情,例如二进制搜索等)。

让我们看看您的示例查询:

选择 * 来自t1 在哪里key_part1 = ( 选择key_part1 来自t1 订购key_part1 ASC 限制 1 ) 通过key_part2 DESC 订购 限制 1 个

这可以在 key_part2 上排序,因为所有返回的行都将具有相同的 key_part1。所以基本上mysql可以忽略索引的那部分;它在功能上与ORDER BY key_part1 DESC, key_part2 DESC 相同。 ORDER BY 在子查询中的方向无关紧要,因为它在子查询中。

编辑

要清楚,您的示例查询实际上如下所示:

选择 * 来自t1 WHERE key_part1 = #某个值 通过key_part2 DESC 订购 限制 1 个

#some value 是子选择的结果。现在应该清楚为什么这种排序不需要文件排序,因为您根本没有按key_part1 排序。其实没必要,因为所有返回的行都会有相同的key_part1

【讨论】:

"子查询中 ORDER BY 的方向无关紧要,因为它在子查询中。"这不是真的;我刚试过(MySQL 5.5)。如果我在子查询中将ASC 更改为DESC,我会得到不同的结果。 如果我不清楚,我深表歉意。我想您是在问“即使我的子查询按 key_part1 ASC 排序,为什么我可以在没有文件排序的情况下按 key_part2 DESC 排序?”从某种意义上说,子查询不影响外部查询的查询计划是无关紧要的。但我并不是说它不会影响返回的结果。 哦,好吧,没错,我明白为什么没有进行文件排序。我真正要问的是为什么 MySQL 不能避免在我的原始查询中进行文件排序;它不应该足够聪明地意识到它可以将原始查询转换为使用子查询以避免进行文件排序的版本吗? 您的示例中的两个查询不等效。查询 #1 没有 WHERE 子句,因此它可以有多个 key_part1,因此不能转换为等效的子查询。想象有人递给你一本字典:“给我所有以'a'开头的单词,按前两个字符排序”——这很容易。现在尝试“给我所有以 'a' 开头的单词,按前 2 个字符排序,但如果有平局,则按倒数第 3 个字符排序。”如果没有第二遍(文件排序),就无法做到这一点,但这基本上就是您所要求的。 查询 #2 大约是: subquery: "get me all words that start with this 2 letters"子查询的结果由前 3 个字母反向”,因为所有结果的第一个和第二个字母都相同)

以上是关于如果 ASC 和 DESC 混合使用,为啥 MySQL 不能为 ORDER BY 使用索引?的主要内容,如果未能解决你的问题,请参考以下文章

mysql中order by 排序用asc和desc不起作用怎么回事

SQL里面的排序语句desc和ASC有啥区别

SQL里面的排序语句desc和ASC有什么区别

数据库: asc和desc的意思

SQL Server:日期时间、ASC 或 DESC 上的聚集索引

如何使用 Fluent API 通过 ASC/DESC 排序在多列上添加索引?