避免在简单的过滤有序查询中进行文件排序
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
我有一个简单的 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