Oracle Hints是嵌入在SQL注释中的一种特殊指令,用来影响优化器生成执行计划的方式。它并不改变SQL逻辑结果,只是在“怎么查”上给优化器提建议。很多人以为Hints是强制命令,实际上优化器在发现Hint会导致错误结果或成本明显不合理时,依然可能忽略它。理解Hints的工作机制,是写出稳定高效SQL的前提。

一、Hints的基本写法与解析规则
Hints必须写在SELECT、UPDATE、DELETE或INSERT关键字之后的第一个注释块中,格式为/*+ hint_name(argument) */。注意加号前不能有空格,否则会被当成普通注释直接忽略。一个SQL语句中可以有多个Hint,彼此用空格分开。如果Hint名称拼错,或者参数指向不存在的对象,该Hint同样失效,且不会报错,这给排查带来了隐蔽性。
下面是一段典型的带Hint查询,强制通过emp_pk索引访问员工表:
SELECT /*+ INDEX(emp emp_pk) */ empno, ename, sal FROM emp WHERE deptno = 10 AND sal > 5000;
在上面代码中,INDEX这个Hint后面跟了表别名(或表名)和索引名。如果表中根本不存在emp_pk,或者优化器认为全表扫描成本更低且统计信息准确,它可能仍选择全表扫描。因此写Hint时,必须确认索引名拼写、表别名对应关系完全正确。
二、为什么加了Hints查询反而变慢
最常见的原因是统计信息过期。比如某张表昨天只有一百行,今天灌入一百万行,但统计信息没更新,优化器估算走索引成本极低,你又用Hint强制走索引,结果引发大量单块读,反而比全表扫描慢十倍。另一种情况是Hint冲突,例如同时写FULL和INDEX,优化器只能采纳其中一个,另一个静默失效,开发者却以为都生效了。
还有一类坑发生在视图或内联查询中。外层SQL对视图加Hint,但视图内部SQL自己也有访问路径,外层Hint未必能穿透。此时应该用PUSH_PRED或UNNEST等专门Hint,而不是简单写INDEX。下面示例展示如何通过HINT让优化器合并视图:
SELECT /*+ MERGE(v) */ e.ename, d.dname
FROM emp e,
(SELECT deptno, dname FROM dept WHERE loc = 'CHICAGO') v
WHERE e.deptno = v.deptno;
MERGE Hint提示优化器把内联视图和主查询合并,避免先物化视图再关联。如果统计信息显示dept表很小,合并后计划会更优;但若忽略此Hint直接执行,可能多出一层临时表,性能差异明显。由此可见,Hints不是越多越好,而是要针对具体计划缺陷精准施加。
三、常用Hints分类与适用场景
从作用域看,Hints大致分三类:访问路径类(如FULL、INDEX、INDEX_FFS)、关联顺序与方式类(如LEADING、USE_NL、USE_HASH)、以及查询转换类(如MERGE、UNNEST、PUSH_SUBQ)。访问路径类直接影响单表读取,关联类决定多表join的嵌套与哈希选择,转换类改变SQL原有结构。
| Hint类型 | 代表语法 | 典型用途 |
|---|---|---|
| 访问路径 | INDEX(t idx_name) | 强制索引扫描 |
| 关联方式 | USE_HASH(a b) | 两表走哈希连接 |
| 查询转换 | UNNEST | 子查询展开提升 |
以USE_NL为例,它要求优化器以嵌套循环方式关联两张表,适合驱动表小、被驱动表有高效索引的场景。但如果误把大表当驱动表,循环次数爆炸,响应时间会急剧上升。因此使用关联类Hint前,先用LEADING固定驱动表顺序,再配合USE_NL才稳妥。
四、如何验证Hints是否真的生效
最可靠的办法是看实际执行计划。通过EXPLAIN PLAN或者DBMS_XPLAN.DISPLAY_CURSOR获取带Note部分的输出,若看到“hint ignored”或计划操作与Hint不符,就说明没采纳。也可以开启SQL_TRACE,结合tkprof分析物理读与逻辑读是否如预期下降。
EXPLAIN PLAN FOR SELECT /*+ INDEX(emp emp_pk) */ * FROM emp WHERE empno = 7788; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
上述代码先生成计划,再格式化输出。在输出里应看到INDEX RANGE SCAN或UNIQUE SCAN,且对象名是emp_pk。如果仍是TABLE ACCESS FULL,就要检查统计信息、别名以及Hint位置。只有经过验证的Hint,才能留在生产SQL中,否则就是一颗定时炸弹。
五、使用Hints的几点原则
第一,永远先让优化器自己选,只有在确认计划错误时才介入。第二,Hints要随统计信息变化定期复审,不能一次写好就永久保留。第三,尽量用具体对象名,少用ALL_ROWS或FIRST_ROWS这类全局 Hint 掩盖真实问题。第四,把Hints和绑定变量区分开,Hints不改变SQL ID,但错误的Hard Parse会引发性能抖动。
当系统升级到新版本优化器后,旧Hint可能不再适用。例如12c引入自适应计划,某些固定Hint会关闭自适应特性,反而损失自动纠错能力。所以写Hint时,也要在注释里写明原因和预期计划,方便后续维护者理解当初为何强制某路径。只有这样,Oracle Hints才是调优利器,而非甩锅工具。