直接结论:大表分区扫描i/o瓶颈80%以上源于direct path read等待事件;关键在优化扫描方式而非sql写法,需通过分区剪枝、禁用_serial_direct_read、存储条带化等手段实现“扫得更少、更稳、更可控”。
直接结论:大表分区扫描的i/o吞吐瓶颈,80%以上源于 direct path read 等待事件,而非sql写法本身;关键不在“要不要扫”,而在“怎么扫得更少、更稳、更可控”。
为什么分区表全扫还是慢?——direct path read 是真凶
Oracle 11g 对大表(默认阈值约 5×db_cache_size)启用直接路径读,绕过 Buffer Cache,直通磁盘。这本为减少 latch 争用,但副作用明显:
- 无法利用已缓存的数据块,每次都是物理 I/O
- 大量并发扫描会触发磁盘队列堆积,
Disk Busy%轻易飙到 100% - AWR 中
direct path read占 DB Time >70%,基本可锁定为 I/O 吞吐主因 - 即使只查单个分区,若该分区仍属“大分区”,同样触发直接路径读
分区键设计必须匹配查询模式,否则等于没分
常见错误是按时间范围分区,但业务查询却总带 WHERE status = 'PENDING' —— 这种谓词无法剪枝(partition pruning),Oracle 仍需逐个检查所有分区头,再挨个扫描每个分区数据块。
- 确认执行计划中是否出现
PARTITION RANGE SINGLE或INLIST ITERATOR,而非PARTITION RANGE ALL - 对高频过滤字段(如
tenant_id,region_code)优先建组合分区键,例如:RANGE-LIST分区,外层按create_time,内层按tenant_id - 避免在分区键上使用函数,如
WHERE trunc(create_time) = ...,会导致无法剪枝 - 检查
dba_tab_partitions中各分区行数是否严重倾斜;单个分区过大(>500万行)时,考虑子分区或再拆分
禁用自动 direct path read,强制走缓存路径
11g 默认策略激进,对稍大的分区也会走 direct path。可通过隐含参数压制:
- 设置
_serial_direct_read=NEVER(需重启实例),让串行扫描回归db file scattered read,复用 Buffer Cache - 搭配增大
db_cache_size(至少覆盖热点分区的 20% 数据量),避免频繁换出 - 注意:此设置不适用于超大数据仓库场景(TB级+并行扫描),仅针对 OLTP/混合负载中“大但非海量”的分区表
- 验证是否生效:查
v$session_event,确认direct path read次数归零,db file scattered read上升且平均等待下降
物理存储层必须配合条带化与IO对齐
分区只是逻辑切分,若底层存储未优化,I/O 仍卡在单盘上:
- 确保表空间数据文件分散在多个物理磁盘(或 ASM diskgroup 多 failure group),避免单点饱和
- 条带深度(stripe depth)建议设为
1MB,与典型多块读大小(db_block_size × db_file_multiblock_read_count,常为 8K×128 = 1MB)对齐 - 禁用操作系统层面的 read-ahead(如 Linux
blockdev --setra 0),防止 direct path read 触发无效预读,加重磁盘压力 - 对高并发扫描场景,考虑将分区表与索引置于不同 ASM diskgroup,避免读写混争
真正难的不是加分区或调参数,而是确认每一条查询是否真的只触达它该触达的那个分区——哪怕只差一个绑定变量类型(NUMBER vs VARCHAR2),都可能让分区剪枝彻底失效。











