子查询未加WHERE条件会触发全表扫描;应显式指定列、避免函数操作索引列、用NOT EXISTS替代NOT IN,并确保统计信息最新。

子查询里没加WHERE条件,直接触发全表扫描
Oracle执行 INSERT ... SELECT 时,子查询部分的执行计划完全独立于外层INSERT。如果子查询没写 WHERE、没走索引、或用了导致索引失效的写法(比如 UPPER(name) = 'ABC'),优化器大概率选全表扫描——哪怕目标表很小,源表一千万行,照样扫完。
实操建议:
- 先单独运行子查询,用
EXPLAIN PLAN FOR看执行计划,确认是否走了索引扫描(INDEX RANGE SCAN或INDEX UNIQUE SCAN),而不是TABLE ACCESS FULL - 避免在子查询
WHERE中对索引列做函数操作,例如把WHERE create_time > SYSDATE - 7写成WHERE TO_CHAR(create_time, 'YYYYMMDD') = '20260617',后者必然丢索引 - 如果子查询含
JOIN,确保关联字段都有索引,且连接顺序合理;必要时用/*+ LEADING(t1) USE_NL(t2) */控制驱动表
INSERT INTO ... SELECT 没指定列名,隐式触发全表扫描
写成 INSERT INTO t1 SELECT * FROM t2 WHERE ... 看似简洁,但 Oracle 会先解析 t2 的全部列定义,再匹配目标表结构。若 t2 有 LOB、XMLTYPE 或未压缩的 LONG 列,即使 WHERE 条件精准,优化器也可能放弃索引而走全表扫描——因为某些数据类型无法被索引覆盖,访问路径被迫退化。
实操建议:
- 显式列出所有需要插入的列:
INSERT INTO t1(col1, col2, col3) SELECT c1, c2, c3 FROM t2 WHERE ... - 检查子查询中是否引用了非索引列(尤其是
SELECT *带出的ROWID、虚拟列、函数列),这些都可能干扰优化器选择索引路径 - 确认
t2上用于过滤的列确实建了索引,且统计信息是新鲜的(DBMS_STATS.GATHER_TABLE_STATS最近一周内执行过)
子查询含 NOT IN 或 !=,让索引彻底失效
NOT IN (SELECT ...) 或主查询里写 col != 'X' 这类否定条件,在 Oracle 19c 中默认不走索引(除非改写为 col 'X')。更麻烦的是,如果子查询返回 NULL,NOT IN 整个结果集变空,优化器干脆放弃索引,回退到全表扫描。
实操建议:
- 用
NOT EXISTS替代NOT IN:它能利用索引,且对NULL安全 - 避免
!=和IS NULL组合;若必须判断空值,确保该列上有函数索引(如CREATE INDEX idx_col_null ON t2 (NVL(col, 'NULL_FLAG'))) - 测试时在子查询末尾加
AND rownum 快速验证逻辑,但上线前务必删掉——<code>rownum会干扰执行计划生成
分区表 + 子查询谓词未下推,白建分区
对按时间分区的大表(如 PARTITION BY RANGE(create_time)),如果子查询的 WHERE 条件没精确匹配分区键(比如用 TO_DATE('2026-06', 'YYYY-MM') 而不是直接写 create_time >= DATE '2026-06-01' AND create_time ),Oracle 可能无法裁剪分区,最终扫描所有分区——等效于全表扫描。
实操建议:
- 分区键过滤必须用字面量或绑定变量,避免嵌套函数;日期类型优先用
DATE字面量而非TO_DATE - 开启
ALTER SESSION SET "_subquery_pruning_enabled" = TRUE(19c 默认开启,但某些补丁版本可能关闭) - 查
PLAN_TABLE的OBJECT_PARTITION_START和OBJECT_PARTITION_STOP字段,确认实际扫描了哪些分区
真正卡住性能的,往往不是 INSERT 动作本身,而是子查询那句 SELECT 被优化器“误判”了访问路径。别只盯着 INSERT 语法,先把它拆出来单独调,看执行计划里有没有 FULL —— 有,就从那里下手。


















