加了/+ INDEX /仍全表扫描的根本原因是优化器基于成本判断弃用索引,常见于函数使用、NULL值过多、统计信息过期、索引名错误、OR/NOT IN条件或未满足最左前缀原则。
为什么加了 /*+ INDEX */ 还是走全表扫描
常见现象是写了索引提示但执行计划里仍显示 table access full,根本原因往往不是语法错,而是优化器“不买账”。oracle 在某些条件下会直接忽略提示:比如被引用的列上有函数、字段为 null 值比例过高、统计信息过期、或者提示中索引名拼写错误(注意大小写和同义词)。更隐蔽的是,如果查询用了 or 条件或 not in,即使提示存在,优化器也可能判定索引访问成本更高而弃用。
实操建议:
- 先查
USER_INDEXES和USER_IND_COLUMNS确认索引名和字段顺序完全匹配,尤其注意复合索引的列序必须与WHERE条件前缀一致 - 运行
DBMS_STATS.GATHER_TABLE_STATS更新统计信息,旧统计会让优化器误判索引选择率 - 用
EXPLAIN PLAN FOR ...+SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)查看真实执行计划,别只信 PL/SQL Developer 或 SQL*Plus 的“自动执行计划” - 如果提示中用了别名,确保
INDEX(t idx_name)的t和FROM子句中的表别名完全一致
INDEX 提示在复合索引和函数索引下的行为差异
复合索引不是“只要命中一列就生效”,它严格依赖最左前缀原则。例如索引 idx_emp_dept_job 建在 (dept_id, job_id, salary) 上,WHERE job_id = 'CLERK' 单独出现时,/*+ INDEX(e idx_emp_dept_job) */ 依然不会走索引——因为跳过了首列 dept_id。
函数索引更敏感:提示必须和定义完全一致。建了 CREATE INDEX idx_upper_name ON emp(UPPER(name)),那提示就得写 /*+ INDEX(e idx_upper_name) */,且查询条件必须是 UPPER(name) = 'SCOTT';写成 name = 'scott' 就无效。
实操建议:
- 对复合索引,检查
WHERE条件是否覆盖了索引最左连续列,否则提示只是摆设 - 函数索引的提示生效前提是:查询中调用的函数、参数顺序、大小写、甚至空格都和索引定义一模一样
- 用
SELECT COLUMN_EXPRESSION FROM USER_IND_EXPRESSIONS查函数索引实际存储的表达式文本,比凭记忆靠谱
全表扫描未必慢,强行用 INDEX 提示反而拖垮性能
当表数据量小(比如 15% 总行数)、或索引列重复值极高(如 status 只有 'Y'/'N'),走索引再回表可能比直接全表扫描多几倍逻辑读。这时硬加 /*+ INDEX */ 不仅没提速,还可能让执行计划固化在低效路径上,后续数据增长后问题更隐蔽。
实操建议:
- 用
AUTOTRACE TRACEONLY STATISTICS对比逻辑读(consistent gets)和物理读,而不是只盯“走了索引”这个表象 - 对高重复值字段,优先考虑位图索引(OLAP 场景)或添加
WHERE rownum <= N限制结果集后再评估提示必要性 - 生产环境慎用提示——它绕过 CBO 决策,一旦统计信息更新或数据分布变化,容易从“刚好快”变成“稳定慢”
替代 INDEX 提示的更稳方案:统计信息 + 查询重写
比起在 SQL 里塞提示,调整统计信息粒度或改写查询结构,往往更可持续。比如把 WHERE col LIKE '%abc' 改成 WHERE col LIKE 'abc%',就能激活索引;或者对 IN 列表过大导致优化器放弃索引的情况,拆成多个 UNION ALL 子查询,每个子查询都能走索引。
实操建议:
- 对模糊查询,优先用
INSTR(col, 'abc') > 0配合函数索引,比LIKE '%abc'更可控 - 用
DBMS_STATS.SET_TABLE_PREFS设置INCREMENTAL和APPROXIMATE_NDV,让统计更准,减少优化器误判 - 批量作业中若必须用提示,统一收口到视图或包内,避免散落在几十个脚本里,后期维护成本太高
真正难的不是写对 /*+ INDEX */,而是判断此刻该不该用它——这取决于数据分布、访问模式、以及你有没有看过真实的 consistent gets 和 physical reads。提示是手术刀,不是创可贴。










