物理读高不等于全表扫描,但结合Physical Reads Per Exec、执行次数、Rows Processed Per Exec、执行计划及db file scattered read等待事件可精准识别;小表全扫未必异常。

看AWR报告里的“SQL Ordered by Physical Reads”
物理读高不等于全表扫描,但它是关键线索。AWR中SQL Ordered by Physical Reads页面列出单位时间内读取物理块最多的SQL,如果某条SQL的Physical Reads Per Exec远高于其Buffer Gets Per Exec(比如前者是后者的80%以上),且该SQL访问的是大表,基本可怀疑在走TABLE ACCESS FULL。
注意:不能只看总物理读。要结合执行次数——一条每秒执行50次、每次读100块的SQL,比一条每天执行1次、每次读50000块的SQL更值得优先排查。
- 重点筛
Executions ≥ 5且Physical Reads Per Exec > 500的条目 - 跳转到该
SQL ID详情页,确认Rows Processed Per Exec是否异常低(如返回1行却读了上万块),这是典型全扫+过滤的信号 - 若
DB Time Per Exec也同步偏高,说明I/O等待已拖累整体响应
查执行计划是否含“TABLE ACCESS FULL”
AWR本身不存完整执行计划,但可通过DBMS_XPLAN.DISPLAY_AWR提取历史计划。只要该SQL在快照期内被执行过,就能还原当时优化器选的路径。
执行以下语句(替换your_sql_id):
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('your_sql_id', NULL, NULL, 'ADVANCED'));结果里逐行检查Operation列,出现TABLE ACCESS FULL即实锤。更要紧的是看它出现在哪一层、对应哪个表名,以及Predicate Information里是否只有filter而无access条件——后者说明索引根本没被用上。
- 若计划中同时存在
INDEX RANGE SCAN和TABLE ACCESS FULL,说明优化器做了多路径试探,但最终选了全扫,大概率因统计信息不准或谓词写法导致索引失效 - 注意
Note部分:若含SQL plan baseline used,说明有固定计划;若无,且PLAN_HASH_VALUE在多个快照中跳变,说明优化器反复“改主意”,全扫可能是参数变化触发的
交叉验证ASH里的等待事件
AWR告诉你“哪条SQL可能扫了表”,ASH告诉你“它扫的时候卡在哪”。仅靠AWR无法区分是慢索引扫描还是真·全表扫描,db file scattered read这个等待事件才是直接证据。
运行以下查询(替换&sid为可疑SQL的会话ID或从AWR中提取的SQL_ID):
SELECT DISTINCT o.object_name, o.object_type, s.blocks * 8 / 1024 AS mb_size FROM v$active_session_history ash JOIN dba_objects o ON ash.current_obj# = o.object_id JOIN dba_segments s ON o.owner = s.owner AND o.object_name = s.segment_name WHERE ash.sql_id = '&sql_id' AND ash.sample_time > SYSDATE - 2/1440 AND ash.event = 'db file scattered read' AND o.object_type = 'TABLE';
若返回的mb_size明显大于_small_table_threshold(默认约2% buffer cache大小),基本坐实是大表全扫。
-
current_obj#为空?说明等待发生在PL/SQL匿名块、硬解析失败或绑定变量未缓存阶段,此时全扫可能由动态SQL或隐式类型转换引发 - 同一
SQL_ID下,不同SAMPLE_TIME采样点对应的current_obj#频繁切换,说明执行路径不稳定,需检查绑定变量窥探或自适应游标共享是否被干扰
别漏掉“执行计划没变但物理读暴涨”的情况
有时PLAN_HASH_VALUE完全一致,但某天开始Physical Reads Per Exec翻了5倍——这往往不是执行计划问题,而是数据分布突变:比如某字段唯一值从100激增至100万,导致原本高效的索引范围扫描退化为全扫式遍历。
这时要查两件事:
- 对比相邻快照的
Rows Processed Per Exec:若从100行涨到50万行,而WHERE条件没变,说明谓词选择性崩塌,优化器仍按旧统计信息估算,实际却不得不扫更多块 - 查
dba_tab_statistics里该表的last_analyzed和stale_stats:若stale_stats = 'YES',立刻收集统计信息,别等下次自动任务 - 用
DBMS_STATS.LOCK_TABLE_STATS临时锁定关键表统计,防止自动任务在业务高峰误刷失准数据
最易被忽略的是:全表扫描未必是坏事。小表(blocks )走全扫比走索引还快。别一看到<code>TABLE ACCESS FULL就删索引,先看对象大小和访问模式。


















