Oracle 中的 HINT 并不是 SQL 标准语法,而是写在 SQL 注释里的优化器指令,用来影响 CBO 生成执行计划的过程。它的位置必须非常严格:紧跟在 SELECT、INSERT、UPDATE、DELETE 等关键字之后,以 /*+ 开头、以 */ 结尾。虽然优化器在大多数场景下能选择较优计划,但遇到统计信息滞后、复杂多表连接、视图合并失误或分页查询排序过重时,借助 HINT 可以快速固定执行路径、降低 SQL 耗时。

一、Oracle HINT 基础语法与生效条件
HINT 的标准写法是 /*+ hint1 hint2 */,多个提示之间用空格分隔。它必须紧跟在 SELECT、INSERT、UPDATE、DELETE 关键字之后,不能放在普通注释位置,否则优化器会把它当作普通注释忽略掉。例如下面的语句通过 ALL_ROWS 提示告诉优化器以最小资源消耗为目标生成计划。
SELECT /*+ ALL_ROWS */
e.employee_id, e.first_name, e.last_name
FROM employees e
WHERE e.department_id = 50;
理解 HINT 的另一个关键是查询块作用域。一个 SQL 可能包含主查询和多个子查询,每个查询块都可以拥有自己的 HINT。外层查询的 HINT 不会自动作用于子查询,反过来也一样。因此当需要对子查询中的访问路径进行干预时,必须在子查询的 SELECT 后单独添加提示。同时,提示中的表名通常要与 SQL 中使用的别名完全一致,否则该提示会被直接忽略。
HINT 有一个容易让人误判的特点:语法写错或者参数不合法时,Oracle 不会报错,而是在优化器解析阶段静默丢弃该提示。这意味着开发人员不能通过是否报错来判断 HINT 是否生效,必须结合执行计划进行验证。可以使用 DBMS_XPLAN.DISPLAY_CURSOR 查看实际执行计划,并观察是否采用了指定索引、连接方式或并行度。
二、常用访问路径与连接方式提示
访问路径提示决定优化器以何种方式读取表或索引。最常用的有 FULL、INDEX、INDEX_FFS 和 NO_INDEX。FULL(表名或别名) 强制全表扫描,适合小表或需要读取大部分数据块的场景;INDEX(表名 索引名) 则强制使用指定索引,常用于统计信息不准导致优化器错误选择全表扫描的情况。需要注意 HINT 中的表名应使用 SQL 中的别名,否则提示会被忽略。
例如在员工表中按邮箱查询时,如果默认走了全表扫描,可以使用 INDEX 提示固定唯一索引,从而快速定位到单行记录。
SELECT /*+ INDEX(e idx_emp_email) */
employee_id, first_name, last_name
FROM employees e
WHERE e.email = 'SKING';
对于只需要读取索引列就能满足查询的 SQL,INDEX_FFS 可以指示优化器使用索引快速全扫描,而不是先读索引再回表。这种提示常用于统计类查询,因为索引块通常比表块小,能减少 I/O。连接方式提示则影响表之间的关联算法:USE_NL 表示嵌套循环连接,适合小表驱动大表;USE_HASH 表示哈希连接,适合两个较大结果集关联;USE_MERGE 表示排序合并连接,适合数据已经有序或者需要处理非等值连接的情况。
连接顺序和连接算法往往需要组合使用。以下示例中,LEADING(d) 指定先访问 departments 表,USE_NL(e) 强制后续使用嵌套循环读取 employees 表,INDEX(e idx_emp_dept_id) 则避免在员工表上做全表扫描。
SELECT /*+ LEADING(d) USE_NL(e) INDEX(e idx_emp_dept_id) */
d.department_name, e.first_name, e.last_name
FROM departments d, employees e
WHERE d.department_id = e.department_id
AND d.department_id = 10;
三、查询转换与并行提示实战
查询转换类提示主要解决优化器对视图、子查询的处理策略。视图合并虽然有助于生成更灵活的连接,但有时会因为改写导致谓词无法下推或执行计划变差,此时 NO_MERGE 可以阻止视图合并。类似地,UNNEST 和 NO_UNNEST 控制子查询是否解嵌套,PUSH_PRED 和 NO_PUSH_PRED 控制连接谓词是否推入视图内部。
当视图内部包含聚合逻辑时,优化器可能尝试将外部条件推入视图后再合并,导致聚合被提前或重复处理。通过 NO_MERGE 可以保持视图独立执行,让外层查询先获得聚合结果再过滤。
SELECT /*+ NO_MERGE(v) */
v.dept_id, v.avg_sal
FROM (SELECT department_id AS dept_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id) v
WHERE v.avg_sal > 10000;
并行提示用于充分利用多核 CPU 和磁盘吞吐。对大表全扫、大结果集排序或大型 DDL 操作,PARALLEL 可以显著缩短执行时间。语法为 PARALLEL(表名 并行度),并行度通常建议不超过单实例 CPU 核数的两倍。下面的示例强制员工表使用 4 个并行进程进行扫描。
SELECT /*+ PARALLEL(e 4) */
e.employee_id, e.first_name, e.last_name
FROM employees e
WHERE e.salary > 8000;
此外,GATHER_PLAN_STATISTICS 可以在执行 SQL 时收集更详细的执行统计,适合在会话级调试时用 DBMS_XPLAN.DISPLAY_CURSOR 查看真实行数与估算行数的差异。RESULT_CACHE 提示可以让结果集进入 SQL 结果缓存,适合读取频繁但变更很少的基础数据查询。CARDINALITY 和 DYNAMIC_SAMPLING 则分别用于修正优化器的基数估计和动态采样级别,在直方图缺失或数据倾斜较大时比较有用。
四、HINT 使用注意事项与排查技巧
HINT 最让人困惑的地方是它可能静默失效。常见原因包括:表别名不一致、指定索引不存在或不可用、提示参数不符合语法、查询块被优化器改写、数据库版本不支持该提示等。例如 SQL 中表别名是 e,但提示写成 INDEX(emp idx_emp_email),这个提示不会产生任何作用,也不会报错。
验证提示是否生效不能只看 SQL 是否执行成功,而要通过执行计划进行核对。使用 DBMS_XPLAN.DISPLAY_CURSOR 可以查看真实执行计划,某些版本会在计划底部显示提示报告,列出已使用和未使用的提示。下面的语句可以在 SQL 执行后获取包含执行统计的计划。
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id', NULL, 'ALLSTATS LAST'));
最佳实践是先把统计信息收集准确,再考虑使用 HINT。统计信息不准导致的计划异常,应当优先通过 DBMS_STATS 收集或设置动态采样来解决。只有当统计信息和系统参数都调整到位后,优化器仍然选择错误计划时,才针对关键 SQL 少量添加 HINT。过多使用 HINT 会让 SQL 在数据分布变化后无法自动调整,维护成本很高。对于必须稳定执行计划的场景,可以考虑 SQL Profile、SQL Plan Baseline 或 SQL Patch,这些机制在稳定性上通常优于直接修改 SQL 文本。
Oracle HINTSQL优化优化器提示修改时间:2026-08-30 02:52:11