分区裁剪失效主因是oracle优化器硬解析时无法静态确认分区,表现为partition range all;绑定变量、隐式转换、子查询/join中条件未外显、物化视图或局部索引缺失原生分区键均会导致裁剪失败。

分区裁剪没生效,不是SQL“写得不对”,而是Oracle优化器在硬解析阶段无法静态确认要访问哪些分区——它宁可全扫,也不愿猜错。执行计划里出现 PARTITION RANGE ALL 或 PARTITION START/PARTITION STOP 显示为 KEY 或 ROW LOCATION,就是最直接的信号。
为什么绑定变量会让PARTITION RANGE ALL出现
Oracle 11g 默认不启用自适应游标共享(ACS),硬解析时 :p_dt 的值不可见,CBO 只能按最保守策略处理:扫描所有分区。这不是 bug,是设计行为。
-
v$sql中查IS_BIND_SENSITIVE、IS_BIND_AWARE、IS_SHAREABLE若非全为Y,说明 ACS 没激活,裁剪必然失效 -
_optim_peek_user_binds = TRUE是默认值,但“窥视”只对首次硬解析有效,后续复用计划仍可能全扫 - 加
/*+ INDEX(t idx_dt) */或/*+ USE_NL(t) */不影响裁剪逻辑——裁剪发生在访问路径选择之前 - 唯一能强制指定分区的 hint 是
/*+ PARTITION(t, p202401) */,但它要求你提前知道分区名,且无法适配动态范围
Java 应用中隐式转换悄悄破坏裁剪
JDBC 传参类型不匹配,Oracle 会自动做隐式转换,导致分区键无法被识别为 access predicate,裁剪直接失效。
- 分区键是
DATE类型,却用setString("p_dt", "2024-01-01")→ 触发TO_DATE(?, '...'),执行计划里ACCESS PREDICATES消失,只剩FILTER PREDICATES - 正确做法是用
setDate()或setTimestamp(),确保类型与表定义严格一致 - 检查执行计划:若出现
PARTITION RANGE ALL且A-Rows很低但Buffers极高,基本就是隐式转换在作祟
子查询或 JOIN 让分区条件“掉链子”
外层没显式约束分区键,哪怕子查询里写得再精准,主表照样全分区扫描。
- 错误写法:
SELECT * FROM orders o JOIN (SELECT user_id, MAX(dt) max_dt FROM log GROUP BY user_id) l ON o.user_id = l.user_id WHERE o.order_time > l.max_dt——orders表完全没受dt约束 - 正确写法:外层必须有独立的
WHERE o.dt = '2024-01-01',且不能藏在ON子句里 -
LEFT JOIN场景下,若右表是分区表但左表没限定分区键,右表可能因驱动顺序被全扫 - 避免
WHERE id IN (SELECT partition_col FROM dim),优化器无法将子查询结果用于分区推导
物化视图和局部索引里分区键“隐身”了
物化视图没包含分区键列,或局部索引的 WHERE 条件里分区键被函数包裹,裁剪就彻底失去上下文。
- 物化视图定义中必须显式 SELECT 分区键列(如
CREATED_DATE),否则优化器连“该按什么裁”都不知道 - 局部索引生效前提:WHERE 必须直接引用原生分区键列,
WHERE TRUNC(sale_date) = ...或WHERE sale_date + 1 > SYSDATE都会导致PARTITION RANGE消失 - 用
DBMS_MVIEW.EXPLAIN_REWRITE查物化视图重写失败原因,重点看MESSAGE字段是否含partition key not used - 执行计划中看不到
PARTITION RANGE SINGLE或ITERATOR,就说明局部索引根本没参与,别浪费时间调索引参数
真正卡住裁剪的,往往不是语法错误,而是那些“看起来应该可以”的写法:函数包装、类型松动、条件藏在嵌套里、或者以为 hint 能兜底。每一步都要看执行计划里的 PARTITION START/PARTITION STOP,而不是相信 SQL 表面逻辑。











