mysql 8.0 中真正起作用的索引 hint 是写在 /*+ ... */ 注释里的原生 hint,如 use_index(t1 idx_name)、force_index(t1 idx_name) 等;老式语法(如 force index (idx))虽兼容但优先级低、作用域窄,且不参与优化器早期决策。

MySQL 8.0 中哪些索引 Hint 能真正起作用
只有写在 /*+ ... */ 注释里的 Hint 才是 MySQL 8.0 原生支持的、语句级生效的可靠方式;老式语法如 USE INDEX (idx) 或 FORCE INDEX 仍可用,但属于 SQL 标准兼容层,不参与优化器决策链的早期干预,优先级低于注释式 Hint。
关键区别在于:注释式 Hint 在解析阶段就被注入优化器决策路径,而传统 Index Hint 是在“访问路径选择”阶段才介入,且不支持多表、子查询等复杂作用域。
-
USE_INDEX(t1 idx_name):建议优化器从指定索引中选,但可被忽略 -
FORCE_INDEX(t1 idx_name):强制使用该索引,无匹配则报错ER_KEY_DOES_NOT_EXITS -
IGNORE_INDEX(t1 idx_name):明确排除某索引,哪怕它看起来最优 -
NO_INDEX_MERGE(t1):禁用索引合并,避免优化器把多个单列索引强行组合
Hint 必须紧贴 SELECT(或 UPDATE/DELETE)关键字后
这是最容易导致 Hint 失效的硬性规则。任何换行、空格、注释分隔都会让 MySQL 完全忽略该 Hint。
错误写法:SELECT /*+ USE_INDEX(orders idx_user_id) */ * FROM orders WHERE user_id = 123;
正确写法(无换行、无空格):SELECT/*+ USE_INDEX(orders idx_user_id) */* FROM orders WHERE user_id = 123;
- Hint 必须与
SELECT(或UPDATE/DELETE)之间**零字符间隔** - 多 Hint 可并列写在同一注释内:
/*+ USE_INDEX(t1 idx_a) NO_RANGE_OPTIMIZATION(t1) */ - 子查询需单独加 Hint:
SELECT * FROM t1 WHERE id IN (SELECT/*+ MATERIALIZATION */id FROM t2)
表名和索引名大小写必须严格匹配
MySQL 默认按 lower_case_table_names 配置决定是否大小写敏感。若配置为 1(Linux 默认),所有表名自动转小写;但索引名仍按建表时原始大小写存储,SHOW INDEX FROM t1 输出的 Key_name 字段就是唯一权威来源。
- 查索引名最稳方式:
SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = 'orders' AND TABLE_SCHEMA = 'mydb'; - 主键索引名固定为
PRIMARY,不是pk或id - 联合索引名不含字段顺序提示,
idx_a_b就是索引名本身,不能写成idx_a_b(a,b) - Hint 中不加引号:
/*+ USE_INDEX(orders IDX_USER_ID) */—— 若实际索引名为idx_user_id(小写),此处大写即失效
Hint 不报错但可能静默失效,务必用 EXPLAIN 验证
Hint 语法错误、表/索引不存在、条件不匹配时,MySQL 不报错,只发 warning 并退回到默认计划。这意味着你写了 Hint 却没生效,还浑然不知。
验证步骤必须闭环:
- 执行
EXPLAIN FORMAT=TREE(MySQL 8.0+ 推荐)或EXPLAIN FORMAT=TRADITIONAL - 检查输出中的
possible_keys和key是否与 Hint 一致 - 立即执行
SHOW WARNINGS,确认有无Warning | 1064 | Ignoring hint...类提示 - 生产环境加 Hint 后,必须对比加前后的
rows、filtered、Extra字段变化
真正麻烦的是:Hint 生效了,但执行计划变差——比如强制走一个低选择性索引,导致回表放大。这比没生效更危险,因为看起来“成功”了。











