MySQL 使用索引排序 ORDER BY 不走 filesort

FreeGuideOnline 最新 2026-07-05

sql SELECT * FROM t ORDER BY a, b, c; SELECT * FROM t WHERE a = 10 ORDER BY b, c; SELECT * FROM t WHERE a = 10 AND b > 5 ORDER BY b, c;


**规则解释**:当你提供对索引前导列的等值条件后,`ORDER BY` 就可以从下一个列开始利用索引的有序性。但绝不能跳过中间的列。

❌ 不会走索引排序的例子:

```sql
SELECT * FROM t ORDER BY b, c;      -- 缺少前导列 a
SELECT * FROM t WHERE a = 10 ORDER BY c;  -- 跳过了 b

2. 避免混合 ASC 与 DESC(MySQL 8.0 前)

在 MySQL 8.0 之前,索引只能以升序(ASC)存储,因此如果 ORDER BY 中同时出现了 ASCDESC,无法直接利用索引排序。

-- 无法使用索引排序(MySQL 5.7)
SELECT * FROM t ORDER BY a ASC, b DESC;

从 MySQL 8.0 开始,支持降序索引DESC 索引),创建索引时可以指定列的排序方向:

CREATE INDEX idx_a_b ON t (a ASC, b DESC);

这样,ORDER BY a ASC, b DESC 就能完美匹配。注意,此时全倒序 ORDER BY a DESC, b ASC 同样可以利用该索引(反向扫描),但 a ASC, b ASC 则不行。

3. 避免在排序列上使用函数或表达式

如果在 ORDER BY 中对索引列使用函数或运算,索引将失效,必然产生 filesort。

❌ 错误写法:

SELECT * FROM t ORDER BY YEAR(create_time);
SELECT * FROM t ORDER BY price + 0;

✅ 应改写为直接引用列,或者创建一个函数索引(MySQL 8.0.13+ 支持):

CREATE INDEX idx_year ON t ((YEAR(create_time)));

4. 利用覆盖索引(Covering Index)消除回表开销

如果查询需要的所有列都在索引中,Extra 会显示 Using index。此时即使有 ORDER BY,MySQL 也能直接从索引中按序读取数据,连回表都省了。

-- 假设索引 idx_a_b_c (a, b, c)
SELECT a, b, c FROM t WHERE a = 1 ORDER BY b;

这条语句几乎是从索引中直接扫码拿到结果,性能极佳。

如果还需要查询其它列(如 d),那么即便 ORDER BY 走了索引,也需要回表获取整行数据。但只要排序使用索引,仍是很大的优化。

5. 留意 JOIN 与多表排序

在多表关联查询中,ORDER BY 如果引用了不同表的列,通常很难利用单张表的索引排序。最佳实践是:先在一个表上将数据按照索引顺序取出,然后进行关联。必要的话,可以通过派生表或子查询预先排序。

-- 先取主表排序数据,再关联
SELECT t1.*, t2.*
FROM (
    SELECT id, a FROM t1 WHERE a > 10 ORDER BY a LIMIT 100
) AS sorted_t1
JOIN t2 ON sorted_t1.id = t2.t1_id;

实战诊断与优化步骤

第一步:查看执行计划

EXPLAIN SELECT ... ORDER BY ...;