Oracle 慢 SQL 优化避坑:索引建了为什么依然走全表扫描
很多开发者和 DBA 都遇到过这样的场景:SQL 执行缓慢,检查执行计划发现走了全表扫描,于是第一反应就是"加索引"。但有时候,索引加上去了,执行计划却纹丝不动,依然是全表扫描。这背后往往不是 Oracle 的 Bug,而是优化器基于成本做出的"理性选择"。本文结合实际案例,拆解几个最常见的"坑",帮你少走弯路。
一、隐式类型转换:最隐蔽的索引杀手
这是生产环境最高频的问题之一。例如,某张用户表 user_id 是 VARCHAR2 类型,并建有普通索引,但 SQL 却写成:
ini
SELECT * FROM t_user WHERE user_id = 12345;
Oracle 为了匹配数据类型,会隐式地将字段转换为数字,相当于执行了:
sql
SELECT * FROM t_user WHERE TO_NUMBER(user_id) = 12345;
一旦对索引列使用了函数,索引就会失效,优化器只能选择全表扫描。
避坑方案:严格保证传入参数与字段类型一致,避免依赖数据库的隐式转换。
二、对索引列做函数运算
类似的场景还有对日期字段使用函数:
sql
SELECT * FROM t_order WHERE TRUNC(create_time) = DATE '2026-08-01';
即便 create_time 上有索引,由于使用了 TRUNC 函数,索引同样无法被直接命中。
优化写法:改为范围查询,利用索引的有序性:
sql
SELECT * FROM t_order
WHERE create_time >= DATE '2026-08-01'
AND create_time < DATE '2026-08-02';
三、统计信息过时:优化器的"错误地图"
Oracle 优化器依赖统计信息来估算行数和成本。如果一张表刚导入了百万级数据,但统计信息还是几天前的,优化器会误以为表很小,从而倾向于使用全表扫描。
避坑方案:在大量数据变更后,及时收集统计信息:
ini
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'SCOTT',
tabname => 'T_ORDER',
cascade => TRUE
);
END;
/
四、索引列存在大量 NULL 值
B-Tree 索引不存储全为 NULL 的条目。如果查询条件是 WHERE status IS NULL,且 status 列大部分值为 NULL,优化器会判断走索引的代价高于全表扫描。
避坑方案:
- 使用默认值替代 NULL;
- 或创建组合索引,例如
(status, 0),确保索引能覆盖 NULL 场景。
五、查询返回数据量过大
即使索引可用,如果优化器估算出查询会返回表中超过 15%~20% 的数据,通常也会放弃索引,转而选择多块读的全表扫描。这是正常的成本权衡,而非索引失效。
避坑方案:
- 增加更精确的过滤条件,减少返回行数;
- 对大范围查询,考虑分区表或物化视图。
六、使用了 NOT、!=、<> 等否定条件
这类条件往往无法利用索引的有序性,容易导致全表扫描。例如:
sql
SELECT * FROM t_user WHERE status != 'ACTIVE';
优化思路:
- 改写 SQL,用正向条件替代;
- 或结合业务,将状态值设计得更利于索引过滤。
七、索引本身"不可用"或"不可见"
运维过程中,索引可能被设置为 UNUSABLE(比如分区维护后未重建),或者被标记为 INVISIBLE(用于灰度验证)。此时优化器会直接忽略该索引。
检查方式:
ini
SELECT index_name, status, visibility
FROM user_indexes
WHERE table_name = 'T_ORDER';
八、绑定变量窥视与执行计划固化
在 OLTP 系统中,绑定变量虽能减少硬解析,但第一次硬解析时的"窥视"会影响后续所有执行的执行计划。如果第一次传入的值选择性极差,优化器可能生成全表扫描的计划并被缓存。
应对方式:
- 使用绑定变量分级(Bind Aware);
- 或在必要时通过 SQL Profile、Baseline 固定更优计划。
九、复合索引未遵循最左前缀原则
例如索引为 (a, b, c),但查询只用了 b 和 c,或者条件中 a 使用了范围查询,导致后续列无法利用索引。
避坑方案:根据高频查询语句设计复合索引的列顺序,把等值查询的列放在前面。
十、CBO 的"成本误判"
有时候,索引和统计信息都正常,但优化器仍然选错。这往往与系统参数(如 db_file_multiblock_read_count)、硬件性能模型有关。
兜底手段:
- 使用
/*+ INDEX(table_name index_name) */强制走索引(需谨慎,仅作为临时手段); - 通过 SQL Tuning Advisor 获取优化建议。
实战排查清单
遇到"索引建了却走全表扫描"的问题,可以按以下顺序快速定位:
EXPLAIN PLAN查看执行计划,确认是否真走了全表扫描;- 检查 WHERE 条件中是否存在函数、隐式转换、否定条件;
- 确认统计信息是否最新;
- 检查索引状态(USABLE / VISIBLE);
- 估算查询返回行数占全表比例;
- 必要时使用 10053 事件跟踪优化器决策过程。
结语
索引不是"银弹",建了索引却走全表扫描,本质上是 Oracle 优化器在成本、数据分布、统计信息等多重因素下做出的综合判断。理解这些"坑",不仅能快速定位慢 SQL 根因,更能让我们在设计表结构、编写 SQL 和运维数据库时做出更合理的决策。