
好的,我们来详细剖析一下 Oracle 数据库中的 db file sequential read 等待事件。这是 Oracle 中最常见、最重要的等待事件之一,理解它对于性能调优至关重要。
1. 什么是 db file sequential read 等待事件?
- 本质: 它表示一个会话正在等待一个单块(Single Block)的物理 I/O 读取操作完成。
- 名字解析:
db file: 表示操作的对象是数据库文件(数据文件、临时文件、控制文件等,但最常见的是数据文件)。sequential read: 这个名字有点历史原因造成的“误导性”。它并不意味着读取的数据块在物理磁盘上是连续的(虽然有时可能是)。它主要表示这次 I/O 操作读取的是单个、逻辑上连续的块(相对于db file scattered read的多块、逻辑上可能不连续的读取)。更准确的理解是“单块读”。
- 核心特征: 会话需要的数据块不在 Buffer Cache 中,必须从磁盘读取。会话在发出读取请求后,会进入等待状态,直到操作系统将请求的数据块读入 Buffer Cache。
- 等待参数: 通过
P1,P2,P3参数标识具体等待:P1: 被读取的文件号 (file#)。P2: 被读取的块号 (block#)。P3: 请求的块数。对于db file sequential read几乎总是 1(多块读是db file scattered read)。
2. 产生的过程
当一个会话执行 SQL 语句(或 PL/SQL 等)需要访问数据时:
- 逻辑读 (Logical Read): 会话首先在 Buffer Cache 中查找所需的数据块。
- Buffer Cache 命中: 如果块在 Buffer Cache 中(
buffer is pinned),会话可以立即访问它,不会产生物理 I/O 等待。 - Buffer Cache 未命中: 如果块不在 Buffer Cache 中(
cache miss),则需要发生一次物理 I/O。 - 发出物理读请求: Oracle 服务器进程(Server Process)代表用户进程(User Process)向操作系统发出读取特定文件(
file#)、特定块(block#)的请求(通常是 1 个块)。 - 进入等待: 发出请求后,服务器进程无法继续处理,必须等待操作系统完成磁盘 I/O 操作并将数据块读入 Buffer Cache。此时,该会话的状态变为
WAITING,等待事件就是db file sequential read。 - I/O 完成: 操作系统完成磁盘读取,将数据块放入 Buffer Cache。
- 恢复执行: Oracle 服务器进程收到 I/O 完成的通知,会话从等待状态中恢复,继续处理 SQL 语句(现在可以在 Buffer Cache 中访问到该块了)。
- 一致性读 (如果需要): 如果是查询,可能需要构造读一致性镜像(CR Block)。如果所需的 UNDO 块也不在 Buffer Cache 中,则可能触发额外的
db file sequential read来读取 UNDO 段块。
3. 哪些场景会触发 db file sequential read
任何需要访问单个不在 Buffer Cache 中的数据库块的场景都可能触发它,最常见于:
- 索引访问:
- 索引唯一扫描 (INDEX UNIQUE SCAN): 通过唯一索引查找单行数据(如
WHERE primary_key = ...)。 - 索引范围扫描 (INDEX RANGE SCAN): 扫描索引的一部分(如
WHERE indexed_column BETWEEN ... AND ...)。每次通过索引 ROWID 访问表时,通常需要一次sequential read读取表块(除非表块已在 Cache 中)。 - 索引全扫描 (INDEX FULL SCAN): 按顺序读取整个索引(虽然读索引块本身可能触发
scattered read,但通过索引 ROWID 回表访问仍然是sequential read)。
- 索引唯一扫描 (INDEX UNIQUE SCAN): 通过唯一索引查找单行数据(如
- 表访问 (通过 ROWID): 当通过 ROWID(通常来自索引扫描)直接访问表中单个特定行时。这是最常见的与表相关的场景。
- 读取数据块头/元数据: 访问段头(Segment Header)、位图块(Bitmap Block)、自由列表块(Free List Block)等管理信息块。
- 读取 UNDO 块:
- 构造读一致性镜像(CR Block)。
- 回滚事务。
- 执行闪回查询。
- 读取 LOB 数据 (IN ROW): 如果 LOB 数据是内联存储(
IN ROW)在表块中的。 - 读取控制文件信息: 某些操作需要读取控制文件块。
- 读取临时文件块 (有时): 虽然排序、哈希连接等大量操作通常使用
direct path read或direct path read temp,但某些小规模或特殊的临时表空间操作也可能触发sequential read。
4. 可能的原因
db file sequential read 等待高并不总是坏事(如 OLTP 中适量的索引访问是正常的),但当它成为系统的主要瓶颈(Top Wait Event)或等待时间异常长时,就需要调查。原因通常归结为:
- A. 必要的物理 I/O (正常或设计使然):
- 应用程序逻辑需要访问大量不同的、离散的数据块(典型的 OLTP 索引访问模式)。
- 数据库刚启动,Buffer Cache 是冷的,需要大量加载。
- 执行了需要访问大量非缓存数据的查询(如大报表,但更适合用
scattered read/direct path read讨论)。
- B. SQL 执行效率低 (根本原因):
- 糟糕的执行计划: 优化器选择了索引扫描,但实际返回的行数非常多(高
rows/cardinality估算错误)。这意味着需要访问大量的表块(每个块一个sequential read)。典型例子是应该走全表扫描却走了索引扫描。 - 低效的索引: 索引选择性差,导致扫描范围过大,回表次数过多。
- 索引缺失: 本应通过高效索引访问的查询被迫走了全表扫描(但全扫主要引发
scattered read,不过如果只访问少量块也可能看到sequential read)。 - Nested Loops Join 驱动不当: 如果驱动表返回的行数太多,对于被驱动表的每次访问(通常通过索引)都会产生一次
sequential read,导致总等待剧增。 - 过度解析或 PL/SQL 循环中执行 SQL: 导致重复执行相同的低效 SQL。
- 糟糕的执行计划: 优化器选择了索引扫描,但实际返回的行数非常多(高
- C. Buffer Cache 不足或管理问题:
- SGA 配置过小:
DB_CACHE_SIZE设置太小,无法容纳工作集(Working Set),导致频繁的 Cache 淘汰和重新加载。 - 热块冲突: 多个会话频繁争相访问同一个或少数几个块(如小表、索引根/分支块、序列相关的块、LATCH FREE 等待伴随出现)。虽然单个
sequential read时间可能正常,但总的等待时间会很高。 - 全表扫描过多: 大量全扫 (
db file scattered read) 会冲刷 Buffer Cache,挤出对 OLTP 工作负载重要的索引和热表块,间接导致后续的索引访问需要更多的sequential read。
- SGA 配置过小:
- D. I/O 子系统性能差 (硬件/存储层):
- 磁盘速度慢: 使用低速磁盘(如 SATA HDD)。
- I/O 路径瓶颈: HBA 卡、存储控制器、存储网络(如 SAN 交换机)过载或配置不当。
- 存储阵列问题: RAID 级别不适用(如 OLTP 用 RAID5)、缓存策略不当、磁盘故障导致降级、后台任务(Rebuild, Patrol Read)影响。
- 文件系统/ASM 效率: 文件系统碎片、ASM 重平衡、不合理的条带化(Stripe Size/Depth)。
- 操作系统 I/O 调度或配置: I/O Scheduler 策略不当,
filesystemio_options设置不佳。 - 资源争用: 同一存储上运行的其他应用或数据库消耗了大量 I/O 资源。
5. 详细排查过程
排查是一个系统性的工作,需要结合多种工具和视图:
步骤 1: 确认问题与范围
- 识别 Top Wait Event: 查看 AWR/ASH 报告 (
awrrpt.sql,ashtop.sql)。确认db file sequential read是否在系统级别或特定时间段是主要等待事件。记录其Total Wait Time (s)和Avg Wait (ms)。 - 确定影响范围: 是整个数据库都慢,还是特定模块、特定用户、特定时间段慢?使用 ASH 报告 (
ashrpt.sql) 或查询gv$active_session_history/dba_hist_active_sess_history按sql_id,module,action,user_id等维度聚合等待事件。 - 关注平均等待时间:
Avg Wait (ms)是关键指标:- 正常范围: 通常健康的 OLTP 系统应小于 10ms,理想 < 5ms (SSD/NVMe) 或 < 15-20ms (高速 SAS HDD)。高于此值通常表示 I/O 子系统或配置有问题。
- 异常高 (e.g., > 50ms, > 100ms): 强烈指向 I/O 子系统性能问题或严重争用。
- 正常但总时间长: 平均等待正常但总等待时间长,通常指向 SQL 效率问题(执行次数太多或每次执行需要读取的块太多)。
步骤 2: 定位源头 SQL 和对象
- AWR/ASH SQL 分析:
- 在 AWR 报告的 “SQL Statistics” -> “SQL ordered by Elapsed Time” / “SQL ordered by User I/O Wait Time” / “SQL ordered by Reads” 部分查找消耗大量时间或 I/O 的 SQL。
- 在 ASH 报告中查看 “Top SQL with Top Events” 部分,找到
db file sequential read等待最多的sql_id。 - 注意
Buffer Gets/Executions和Disk Reads/Executions。高Disk Reads/Executions比率是重要线索。
- 查询 v$active_session_history / dba_hist_active_sess_history:
SELECT sql_id, COUNT(*) AS waits, SUM(time_waited)/1000 AS total_wait_ms, AVG(time_waited)/1000 AS avg_wait_ms, session_id, session_serial# FROM gv$active_session_history WHERE event = 'db file sequential read' AND sample_time > SYSDATE - 1/24 -- 例如过去1小时 GROUP BY sql_id, session_id, session_serial# ORDER BY waits DESC, total_wait_ms DESC; - 关联 SQL 文本: 使用找到的
sql_id,查询v$sqltext或dba_hist_sqltext获取完整的 SQL 语句。 - 定位具体文件和块 (实时/历史):
- 实时 (v$session_wait):
SELECT sid, event, p1 AS file#, p2 AS block#, p3 AS blocks, wait_time, seconds_in_wait FROM v$session_wait WHERE event = 'db file sequential read'; - 历史 (v$active_session_history):
这能告诉你会话在等待读取哪个对象的哪个块(SELECT ash.sample_time, ash.session_id, ash.session_serial#, ash.sql_id, ash.current_obj#, o.owner, o.object_name, o.object_type, ash.current_file#, ash.current_block#, ash.time_waited FROM gv$active_session_history ash JOIN dba_objects o ON (ash.current_obj# = o.object_id) WHERE ash.event = 'db file sequential read' AND ash.sample_time > SYSDATE - 1/24 -- 例如过去1小时 ORDER BY ash.sample_time DESC;current_obj#,current_file#,current_block#)。 - 识别热块: 对
file#和block#进行聚合统计,找出被频繁等待读取的热块。查询v$segment_statistics或使用dba_hist_seg_stat查看对象级别的物理读统计。
- 实时 (v$session_wait):
步骤 3: 分析 SQL 执行计划
- 获取执行计划:
EXPLAIN PLAN FOR <your_sql>;然后SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', null, 'ADVANCED ALLSTATS LAST'));(需要实际执行过)- 从 AWR 报告 “SQL Statistics” 部分查看历史执行计划。
- 关键检查点:
- 驱动表行数: Nested Loops Join 的驱动表是否返回了过多的行?
- 访问路径: 是否预期使用了索引?实际使用了哪个索引?索引扫描后是否有大量的回表操作 (
TABLE ACCESS BY INDEX ROWID)? - 预估行数 vs 实际行数 (
A-Rows): 优化器估算 (E-Rows) 和实际返回行数 (A-Rows) 是否差异巨大?巨大差异通常导致错误计划选择。 - 全表扫描: 是否存在本应走索引却走了全表扫描的情况?
- 索引效率: 使用的索引是否高效?选择性如何?是否是组合索引的前导列?
步骤 4: 检查 Buffer Cache 和 I/O 子系统
- Buffer Cache 命中率 (AWR): 查看 AWR 报告 “Instance Efficiency Percentages” 部分的
Buffer Nowait %和Buffer Hit %。持续低于 90-95% 可能表示 Cache 不足,但需结合具体场景分析(如 DW 系统允许更低)。更可靠的是看 “Load Profile” 的Blocks changes/Reads和 “Instance Activity Stats” 的physical reads/ (db block gets+consistent gets)。 - 检查 SGA 配置:
SHOW PARAMETER sga_target/sga_max_size/db_cache_size。当前大小是否足够?与 AWR 建议(如果有)比较。 - I/O 性能指标 (AWR/OS):
- AWR:
- “Load Profile”: 查看
Physical Reads/s,Physical Writes/s,Redo Writes/s。高Physical Reads/s对应高sequential read等待。 - “Instance Activity Stats”: 查看
physical read IO requests,physical read bytes,physical read total IO requests,physical read total bytes。计算平均每次 I/O 请求大小。 - “IOStat by Function summary”: 重点看
Data File Read相关的列:Av Reads/s: 平均每秒读次数。Av Rd(ms): 平均每次读等待时间 (毫秒)。这是最直接反映存储性能的指标!与步骤 1 的Avg Wait (ms)比较。Av Blks/Rd: 平均每次读的块数。对于sequential read应该接近 1。
- “Tablespace IO Stats” / “File IO Stats”: 定位到具体表空间或数据文件级别的 I/O 负载和延迟。找出热点文件。
- “Load Profile”: 查看
- 操作系统工具:
- Linux:
iostat -x 2(关注await,svctm,%util对特定磁盘/LUN)。vmstat 2,sar -d 2。 - AIX:
iostat -DRl 2(关注avgserv),sar -d 2。 - Windows: Perfmon (关注
PhysicalDisk(*)\Avg. Disk sec/Read)。 - 关键指标:
Read Latency(平均读响应时间) 是否与 AWR 报告的Av Rd(ms)一致且过高?Queue Length/%Utilization是否过高?是否存在特定磁盘/LUN 瓶颈?
- Linux:
- AWR:
步骤 5: 综合分析与解决方案
根据以上排查结果,针对不同原因采取相应措施:
- SQL 效率问题 (最常见):
- 优化执行计划: 收集最新、准确的统计信息 (
DBMS_STATS)。考虑使用 SQL Plan Baseline 或 SQL Profile 固定好的计划。使用 Optimizer Hints(谨慎)。 - 优化索引:
- 创建缺失的、高选择性的索引。
- 修改现有索引(如增加组合索引的列,改变列顺序)。
- 考虑索引组织表 (IOT) 或聚簇表 (Cluster) 减少回表。
- 对于回表严重的索引,评估使用覆盖索引 (Covering Index)。
- 重写 SQL: 改变写法避免低效操作(如不必要的 DISTINCT, UNION, 函数包裹列导致索引失效,错误的 JOIN 顺序)。
- 优化 Nested Loops Join: 确保驱动表结果集小且高效。考虑使用 HASH JOIN 或 MERGE JOIN 替代。
- 减少执行次数: 避免在 PL/SQL 循环中执行 SQL,改为批量处理或集合操作。优化应用逻辑。
- 优化执行计划: 收集最新、准确的统计信息 (
- Buffer Cache 不足/管理:
- 增加
DB_CACHE_SIZE: 在SGA_TARGET允许范围内,或在有SGA_MAX_SIZE空间时手动调整。需要评估内存总量。 - 优化 SQL 减少物理读: 这是根本。好的 SQL 对 Cache 需求最小。
- 识别并缓解热块冲突:
- 使用反转索引 (Reverse Key Index) 打散索引叶块热点(对序列主键有效)。
- 增加序列 Cache (
CACHE N) 减少序列相关的索引右增长块争用。 - 考虑分区打散数据。
- 对于小热表,尝试 KEEP Pool (
DB_KEEP_CACHE_SIZE)。 - 调整
PCTFREE/PCTUSED或使用 ASSM 管理空间可能有一定帮助(效果有限)。
- 控制全表扫描影响: 使用
DB_RECYCLE_CACHE_SIZE隔离全扫影响。评估KEEPPool 缓存重要的小表/索引。
- 增加
- I/O 子系统性能差:
- 硬件升级: 迁移到更快的存储(SSD, NVMe)。
- 存储配置优化:
- 确保使用适合 OLTP 的 RAID 级别 (RAID 10)。
- 检查并优化存储阵列 Cache 策略(Write-Back with BBU? Read-Ahead?)。
- 评估并调整条带化 (Stripe Size/Depth) 设置。
- 分离重负载文件到不同的物理磁盘/LUN/控制器。
- 检查并修复磁盘故障或降级状态。
- 文件系统/ASM 优化:
- 确保文件系统没有严重碎片。
- ASM: 使用合适的冗余和分配单元大小 (
AU_SIZE),确保磁盘组平衡完成。
- 操作系统优化:
- 检查并调整 I/O Scheduler (e.g., Linux 的
deadline或noop通常比cfq更适合 SSD/高速 SAN)。 - 设置
FILESYSTEMIO_OPTIONS=SETALL(通常推荐) 或=ASYNCH/=DIRECTIO,启用异步 I/O 和 Direct I/O,避免 Double Buffering。检查disk_asynch_io参数。 - 确保 OS 文件缓存不会干扰(Direct I/O 可避免)。
- 检查并调整 I/O Scheduler (e.g., Linux 的
- 资源管理: 限制同一存储上其他应用的 I/O 带宽。
关键诊断视图总结
- 整体/历史:
V$SYSTEM_EVENT,DBA_HIST_SYSTEM_EVENT, AWR/ASH 报告 - 会话级实时:
V$SESSION,V$SESSION_WAIT,V$SESSION_EVENT - 会话级历史:
V$ACTIVE_SESSION_HISTORY,DBA_HIST_ACTIVE_SESS_HISTORY - SQL 级:
V$SQL,V$SQLSTATS,V$SQL_PLAN,V$SQL_PLAN_STATISTICS_ALL,DBA_HIST_SQLSTAT,DBA_HIST_SQLTEXT - 对象/文件/块级:
V$ACTIVE_SESSION_HISTORY(CURRENT_OBJ#,CURRENT_FILE#,CURRENT_BLOCK#),DBA_OBJECTS,DBA_EXTENTS,V$SEGMENT_STATISTICS,DBA_HIST_SEG_STAT - I/O 统计:
V$FILESTAT,V$TEMPSTAT,V$IOSTAT_FILE,V$IOSTAT_FUNCTION, AWR 的 IOStat 部分 - Buffer Cache:
V$BUFFER_POOL_STATISTICS,V$SGASTAT,V$DB_CACHE_ADVICE
重要提示
- 上下文至关重要:
db file sequential read本身是数据库工作的正常组成部分。关键在于它是否成为瓶颈,以及其平均等待时间和总时间是否异常。 - 关注
Avg Wait (ms): 这是区分是 SQL/应用问题(次数多)还是存储问题(单次慢)的关键指标。 - SQL 优化是王道: 绝大多数情况下,高
db file sequential read等待的根本原因是低效的 SQL 执行计划导致产生了过多不必要的单块物理读。 - 综合判断: 不要孤立地看这个等待事件。结合
enq: TX - row lock contention,latch free,log file sync,cursor: pin S wait on X等其他等待事件,以及 CPU 使用率、内存使用情况一起分析。 - 基准测试: 在对 I/O 子系统或配置做重大变更前后,进行基准测试以量化效果。
通过以上系统的排查步骤和深入理解,你应该能够有效地诊断和解决由 db file sequential read 等待事件引起的性能问题。
欢迎关注我的公众号《IT小Chen》
8447

被折叠的 条评论
为什么被折叠?



