MySQL 8.0的Hint必须紧贴SELECT等关键字才生效,换行、注释中或FROM后均失效;多查询块需QB_NAME命名后定向指定;INDEX/USE_INDEX/NO_INDEX行为差异大,强制走索引需组合使用;JOIN顺序控制需据场景选STRAIGHT_JOIN或JOIN_ORDER。

Hint必须紧贴关键字,否则直接失效
MySQL 8.0 的 /*+ */ 语法只在紧贴 SELECT、UPDATE、INSERT 等语句关键字后才被识别。写在注释块中间、换行后、或插在 FROM 后面,都会被忽略。
常见失效场景:
-
SELECT * /*+ INDEX(t1 idx_a) */ FROM t1→ 注释不在关键字后,不生效 -
SELECT\n/*+ INDEX(t1 idx_a) */ * FROM t1→ 换行导致解析中断,不生效 -
SELECT /*+ INDEX(t1 idx_a) */ * FROM t1 FORCE INDEX (idx_a)→FORCE INDEX优先级更高,Hint 被覆盖
验证是否生效的唯一可靠方式是加 EXPLAIN 前缀再执行:EXPLAIN SELECT /*+ INDEX(t1 idx_a) */ * FROM t1 WHERE ...,然后看 key 和 rows 是否符合预期。
多查询块(UNION / 子查询)必须显式指定 QB_NAME
每个 UNION 分支、子查询、CTE 都是一个独立的 Query Block(编号如 @sel_1、@sel_2)。主查询里的 Hint 默认只作用于 @sel_1,对子查询完全无效。
正确做法是先用 QB_NAME 命名,再定向干预:
- 给子查询命名:
SELECT /*+ QB_NAME(outer) */ * FROM t1 WHERE id IN (SELECT /*+ QB_NAME(inner) */ id FROM t2) - 只为子查询指定索引:
SELECT /*+ QB_NAME(outer) */ * FROM t1 WHERE id IN (SELECT /*+ QB_NAME(inner) INDEX(t2 idx_id) */ id FROM t2) - 跨块控制 JOIN 顺序(MySQL 8.0.20+):
/*+ JOIN_ORDER(@inner t2, t3) */
不命名就写 /*+ INDEX(t2 idx_id) */,MySQL 会尝试匹配 @sel_1 中的表,而 t2 实际在 @sel_2 里 —— 结果就是静默失效。
INDEX / NO_INDEX / USE_INDEX 不是同义词,行为差异极大
这三个看似相似的 Hint 实际干预层级和强度完全不同:
-
/*+ INDEX(t1 idx_a) */:仅“建议”使用该索引,优化器仍可因成本估算放弃它 -
/*+ USE_INDEX(t1 idx_a) */:比INDEX更强,但依然允许优化器选主键或跳过索引扫描 -
/*+ NO_INDEX(t1 idx_b, idx_c) */:必须明确列出所有要禁用的索引名,漏一个就可能被绕过;它不自动排除主键,需单独写NO_INDEX(t1 PRIMARY)
真正“强制走某索引且封死其他路径”的组合是:/*+ USE_INDEX(t1 idx_a) NO_INDEX(t1 PRIMARY, idx_b, idx_c) */。注意索引名大小写敏感,必须和 SHOW INDEX FROM t1 输出的 Key_name 完全一致。
JOIN 顺序控制:STRAIGHT_JOIN 和 JOIN_ORDER 各有适用边界
当驱动表极小、被驱动表索引高效时,STRAIGHT_JOIN 是最简单可靠的方案 —— 它跳过 Join Reordering 阶段,按 SQL 文本顺序硬执行。
但它的粒度太粗:
- 影响整条语句,包括所有子查询
- 无法只固定前两张表,让第三张表仍由优化器评估
这时就得用 /*+ JOIN_ORDER(t1, t2, t3) */(MySQL 8.0.20+),但它只影响成本评估,并不保证最终执行顺序。更精细的控制还需配合 QB_NAME 限定作用域,例如:/*+ JOIN_ORDER(@sel_2 t2, t3) */。
最容易被忽略的一点:Hint 不改变 SQL 语义,也不修复低效 WHERE 条件。哪怕指定了索引,若写了 WHERE YEAR(created_at) = 2024,MySQL 仍会全表扫描 —— 此时应先改写条件,再加 Hint。


















