避免在简单的过滤有序查询中进行文件排序

Avoid filesort in simple filtered ordered query

我有一个简单的 table:

CREATE TABLE `user_values` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL,
  `value` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`,`id`),
  KEY `id` (`id`,`user_id`);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

我正在尝试执行以下简单查询:

select * from user_values where user_id in (20020, 20030) order by id desc;

我完全希望这个查询 100% 使用索引((user_id, id) 一个或 (id, user_id) 一个)然而,结果证明不是案例:

explain select * from user_values where user_id in (20020, 20030); 产量:

id select_type table partitions type key key_len ref rows filtered Extra
1 SIMPLE user_values NULL range user_id 8 NULL 9 100.00 Using index condition; Using filesort

为什么会这样?我怎样才能避免对这个微不足道的查询进行文件排序?

MySQL 很可能会使用索引进行查询(除非查询中的 user_id 涵盖了大部分行)。

“文件排序”发生在内存中(它实际上不是文件排序),用于根据 ORDER BY 子句对找到的行进行排序。

您无法避免显示的查询中的文件排序。

当您使用范围谓词(例如,IN ( ) 是一个范围谓词)并使用索引时,将按索引顺序读取行。但是 MySQL 查询优化器无法猜测按 user_id 索引顺序读取行将保证它们也处于 id 顺序。您正在搜索的两个 user_id 值可能以任何顺序分散在 table 中。因此 MySQL 必须假设一旦匹配的行被读取,一个额外的步骤将结果按 id 排序是必要的。

这是一个假设数据的示例,其中按 user_id 上的索引读取行将 而不是 id 顺序。

id user_id
1 20030
2 20020
3 20016
4 20030
5 20020

因此,当从 (user_id, id) 上的索引读取时,匹配行将按以下顺序 returned,首先按 user_id 排序,然后按 id :

id user_id
2 20020
5 20020
1 20030
4 20030

很明显,结果不是id顺序,所以需要排序才能满足你要求的ORDER BY

其他类型的谓词也会产生同样的效果,例如 BETWEEN<!=IS NOT NULL 等。每个谓词除了= 是范围谓词。

避免文件排序的唯一方法是通过以下方式之一更改查询:

省略 ORDER BY 子句并接受优化器选择 return 它们的任何顺序的结果,这可能是 id 顺序,但只是巧合。

user_id IN (20020, 20030)改为user_id = 20020,这样只有一个匹配的user_id,因此从索引中读取匹配的行已经被return编辑了id 顺序,因此 ORDER BY 是 no-op。优化器识别何时这是可能的,并跳过文件排序。

在这种情况下,您无法避免“排序”。

大约有 9 行要排序,所以不会花很长时间。

查询用了多长时间?可能只有几毫秒,所以谁在乎呢?

“文件排序”并不一定意味着涉及“文件”。在许多查询中,排序是在 RAM 中完成的。

除了 table 上的 PRIMARY KEY 之外,您是否将 id 用于其他用途?如果没有,那么这会有所帮助。 (speed-up 不会显示在 EXPLAIN 中。)

  PRIMARY KEY (`user_id`,`id`),  -- to avoid secondary lookups
  KEY `id` (`id`);   -- to keep auto_increment happy