
prefer_ordering_index 是 MySQL 8.0.21 版本引入的优化器开关参数,通过 optimizer_switch 系统变量控制,其核心作用在于调整优化器对"排序索引"的偏好程度,直接影响执行计划的选择逻辑。

官网文档:https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html
优化器决策的核心矛盾:当查询语句包含 WHERE 过滤条件和 ORDER BY 时,优化器面临两难选择:
选择排序索引:使用能避免额外排序的索引(如 ORDER BY 字段的索引),但可能需要扫描大量不满足 WHERE 条件的数据
选择过滤索引:使用能高效过滤数据的索引(如 WHERE 字段的索引),但需要额外进行排序操作
系统默认:prefer_ordering_index=on(默认)会优先选择排序索引,在高过滤性查询中导致全表扫描,性能可能下降好几倍。
MySQL 优化器通过成本模型计算执行计划公式:

总成本 = 索引扫描成本 + 排序成本 + 回表成本prefer_ordering_index 通过调整排序成本的权重影响决策:
说明:当 WHERE 条件过滤性 > ORDER BY 排序性时(如 status='shipped' 过滤掉 80% 数据),关闭此参数能显著提升性能。
使用 EXPLAIN FORMAT=JSON 可清晰看到参数影响:
EXPLAIN FORMAT=JSON SELECT * FROM orders
WHERE status = 'shipped'
ORDER BY create_time DESC
LIMIT 10;开启参数:possible_keys 显示 idx_create_time,Extra 显示 Using index condition; Using filesort
关闭参数:possible_keys 显示 idx_status,Extra 显示 Using where; Using index
诊断技巧:当
Extra出现Using filesort且rows值远大于filtered时,很有可能是prefer_ordering_index未优化。
以下脚本创建包含 100 万行数据的测试表,模拟电商订单场景:

-- 创建测试表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL,
create_time DATETIME NOT NULL,
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 正确插入100万行数据(使用6个UNION ALL,每个100行)
INSERT INTO orders (user_id, status, create_time)
SELECT
FLOOR(RAND() * 100000) AS user_id,
CASE WHEN RAND() > 0.8 THEN 'shipped' ELSE 'pending' END AS status,
NOW() - INTERVAL FLOOR(RAND() * 365) DAY AS create_time
FROM
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t1,
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t2,
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t3,
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t4,
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t5,
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) t6
LIMIT 1000000;
ANALYZE TABLE orders;

测试脚本说明:
status 字段 80% 为 pending,20% 为 shipped(高过滤性场景)
create_time 覆盖 1 年时间范围(模拟真实业务)
数据量 100 万行(足够展示性能差异)
问题:获取用户订单的倒序分页(第 1000 页,每页 100 条)
SELECT * FROM orders
WHERE status = 'shipped'
ORDER BY create_time DESC
LIMIT 100 OFFSET 99900;默认行为(prefer_ordering_index=on):

idx_create_time 索引,但需扫描大量 status != 'shipped' 的数据
优化后(prefer_ordering_index=off):
SET SESSION optimizer_switch = 'prefer_ordering_index=off';
SELECT * FROM orders
WHERE status = 'shipped'
ORDER BY create_time DESC
LIMIT 100 OFFSET 99900;优化执行计划:

性能:0.12 秒(扫描 2 万行,减少 90% I/O)
关键改进:优化器选择 idx_status 索引过滤 20% 数据,再排序
问题:统计最近 7 天各 IP 的登录次数
SELECT ip, COUNT(*) AS cnt
FROM login_logs
WHERE login_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY ip;表结构:
CREATE TABLE login_logs (
id BIGINT PRIMARY KEY,
ip VARCHAR(15),
login_time DATETIME,
INDEX idx_ip_time (ip, login_time),
INDEX idx_time_ip (login_time, ip)
);默认行为(prefer_ordering_index=on):
idx_time_ip(因 ORDER BY 未显式出现,但 GROUP BY 隐含排序)Extra 显示 Using index; Using filesort → 需额外排序
优化方案:
SET SESSION optimizer_switch = 'prefer_ordering_index=off';优化后效果:
idx_ip_time 索引Extra 显示 Using index → 避免排序(因索引顺序匹配 GROUP BY)问题:获取评分最高的 20 条评论
SELECT * FROM product_comments
WHERE product_id = 1001
ORDER BY score DESC
LIMIT 20;表结构:
CREATE TABLE product_comments (
comment_id BIGINT PRIMARY KEY,
product_id BIGINT,
score TINYINT,
content TEXT,
INDEX idx_product_score (product_id, score)
);默认行为(prefer_ordering_index=on):

问题:优化器选择主键索引,需扫描 100 万行后排序
优化方案:
SET SESSION optimizer_switch = 'prefer_ordering_index=off';优化执行计划:

性能:0.02 秒(扫描 10 行,避免全表扫描)
关键改进:利用 idx_product_score 覆盖索引,直接返回排序结果
prefer_ordering_index?场景特征 | 推荐设置 | 理由 |
|---|---|---|
WHERE 条件过滤性强 | off | 高效过滤 > 排序成本 |
分页深度大(OFFSET > 1000) | off | 避免全表扫描 |
GROUP BY 需排序 | off | 利用索引顺序避免 filesort |
ORDER BY 字段区分度低 | off | 排序成本占比高 |
ORDER BY 字段区分度极高(如时间戳)
WHERE 条件过滤性弱(如 WHERE status IN ('all'))
查询返回结果集小(LIMIT 10)
-- 检查排序操作
SHOW STATUS LIKE 'Handler_sort';
-- 对比优化前/后
SET @start = NOW();
SELECT ...;
SELECT TIMESTAMPDIFF(MICROSECOND, @start, NOW()) AS duration;终极建议:在业务高峰期,对核心查询进行
prefer_ordering_index=off优化,通常能获得 5-10 倍的性能提升,且无需修改表结构或索引。这是 MySQL 8.0 中被严重低估的查询优化技巧!
默认值陷阱:MySQL 默认开启,大部分开发者误以为这是最优配置。
优化器黑河:很多DBA通常只关注 EXPLAIN 的 type 和 key,忽略 optimizer_switch
场景特定性:仅在特定查询模式下有效(高过滤性 + ORDER BY)
行动建议:
下次遇到 ORDER BY 查询性能问题,先检查:
EXPLAIN 中是否出现 Using filesortWHERE 条件是否能高效过滤数据SET SESSION optimizer_switch = 'prefer_ordering_index=off' 验证。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。