force index 是仅在优化器明显误判且索引确认有效时的临时干预手段,必须紧接 from 后、括号内写真实 b-tree 索引名(如 primary 或 idx_status_created),位置或名称错误即报错,滥用反致性能下降。

FORCE INDEX 不是“让 MySQL 用索引”的通用开关,而是仅在优化器明显误判、且你已确认索引有效时的临时干预手段。它会直接拒绝不含该索引的执行计划,写错就报错,滥用反而拖慢查询。
FORCE INDEX 的正确位置和语法格式
必须紧接在 FROM table_name 后面,括号不能省,索引名必须是真实存在的 B-tree 索引名(不是列名,也不是前缀索引的列名简写):
SELECT * FROM orders FORCE INDEX (idx_status_created) WHERE status = 'paid';
- 错误写法:
FROM orders WHERE status = 'paid' FORCE INDEX (idx_status_created)(位置错,语法不识别) - 错误写法:
FORCE INDEX (status)(status是列名,不是索引名) - 错误写法:
FORCE INDEX (idx_status)(索引实际叫idx_status_created,名字不全或拼错 → 报错ERROR 1176 (HY000): Key 'idx_status' doesn't exist in table 'orders') - 主键强制写法:
FORCE INDEX (PRIMARY),注意大写PRIMARY,不是force index (id)
哪些场景下才值得试 FORCE INDEX
不是“加了就快”,而是用于验证性排查或紧急绕过已知缺陷:
-
EXPLAIN显示type=ALL或key=NULL,但 WHERE 条件明显匹配某个索引(比如WHERE created_at > '2025-01-01',而表上有idx_created_at) - 统计信息过期(
ANALYZE TABLE没跑过),导致优化器低估索引价值 - WHERE 中用了函数(如
WHERE DATE(created_at) = '2025-04-10'),导致索引失效,你想验证去掉函数后走索引的效果 - JOIN 多表时,某张被驱动表本该用
ref走索引,却走了ALL;这时需对那张表单独加FORCE INDEX,例如:JOIN logs l FORCE INDEX (idx_device_id_ts) ON l.device_id = d.id
UPDATE 和 DELETE 也能用,但风险更高
语法结构一致,但影响更隐蔽:
UPDATE orders FORCE INDEX (idx_user_id) SET status = 'shipped' WHERE user_id = 123 AND status = 'pending';
- UPDATE/DELETE 若强制了非覆盖索引,可能触发大量回表 + 二级索引更新 + undo log 写入,比 SELECT 更容易卡住
- 没有
EXPLAIN UPDATE,只能靠EXPLAIN FORMAT=TRADITIONAL模拟等价 SELECT,再结合SHOW PROFILE或 slow log 观察实际耗时 - 线上执行前务必在从库或影子表上压测,避免因锁范围扩大引发雪崩
- 不支持在子查询中使用 FORCE INDEX(MySQL 8.0 仍不支持)
为什么有时候加了 FORCE INDEX 反而更慢
强制 ≠ 优化,它绕过了优化器的成本估算,把选择权交给你,而你未必掌握全部上下文:
- 强制的索引字段顺序不匹配 WHERE 最左前缀(如索引是
(a, b, c),但 WHERE 只有b = ?,即使加了 FORCE,MySQL 也用不上) - 索引不覆盖查询字段(SELECT * 或含未包含列),导致每行都要回主键查数据,IO 翻倍
- 强制了低基数索引(如
status只有 3 个值),优化器原本选全表扫描是对的,你硬塞一个索引,反而多一层 B-tree 遍历开销 - 并发高时,强制走某个索引可能加剧热点页争用(比如所有请求都挤在
idx_created_at的最新叶子页)
真正该优先做的,是检查 SHOW INDEX FROM table_name 看索引是否合理、运行 ANALYZE TABLE 更新统计信息、重写 WHERE 避免函数或隐式类型转换——FORCE INDEX 是最后一步,不是第一步。











