首页 > 数据库 > Oracle > 如何从等待事件定位到具体数据块和具体行
排查 I/O 相关的性能问题时,等待事件给出的信息是物理地址: db file sequential read 的 P1 是文件号、 P2 是块号, buffer busy waits 等待同样以文件号加块号标识热点块。 DBA 拿到这组数字后,下一步往往就断了线,这是哪个表的哪个段落?如果要继续追,能不能定位到具体行?
这一步的关键是 ROWID ,以及配套的 DBMS_ROWID 包。 ROWID 本身就是行的物理地址,扩展 ROWID 由 18 个 base64 字符构成,从左到右依次是 6 位数据对象号、 3 位相对文件号、 6 位块号、 3 位行槽号。 DBMS_ROWID 负责这串字符与四段数值之间的双向换算,是纯粹的分析型工具, EXECUTE 权限同样授予了 PUBLIC 。
一、包结构
SQL> desc dbms_rowid FUNCTION ROWID_BLOCK_NUMBER RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROW_ID ROWID IN TS_TYPE_IN VARCHAR2 IN DEFAULT FUNCTION ROWID_CREATE RETURNS ROWID Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROWID_TYPE NUMBER IN OBJECT_NUMBER NUMBER IN RELATIVE_FNO NUMBER IN BLOCK_NUMBER NUMBER IN ROW_NUMBER NUMBER IN PROCEDURE ROWID_INFO Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROWID_IN ROWID IN ROWID_TYPE NUMBER OUT OBJECT_NUMBER NUMBER OUT RELATIVE_FNO NUMBER OUT BLOCK_NUMBER NUMBER OUT ROW_NUMBER NUMBER OUT FUNCTION ROWID_OBJECT RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROWID_ID ROWID IN FUNCTION ROWID_RELATIVE_FNO RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROWID_ID ROWID IN TS_TYPE_IN VARCHAR2 IN DEFAULT FUNCTION ROWID_ROW_NUMBER RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- ROW_ID ROWID IN
| 子程序 | 作用 |
|---|---|
| ROWID_OBJECT | 从 ROWID 中取出数据对象号 |
| ROWID_RELATIVE_FNO / ROWID_BLOCK_NUMBER / ROWID_ROW_NUMBER | 取出相对文件号、块号、行槽号,可直接在 SQL 中按行调用 |
| ROWID_INFO | 一次取出全部四段信息,过程,只能在 PL/SQL 中使用 |
| ROWID_CREATE | 按四段数值反向构造 ROWID,rowid_type 传 1 表示扩展 ROWID、0 表示老式受限 ROWID |
二、实测:分解与还原 ROWID
1. 准备数据(TEST 用户)
SQL> create table t_rowid(id number, pad varchar2(100));
Table created.
SQL> begin
2 for i in 1 .. 10000 loop
3 insert into t_rowid values (i, rpad('x', 100, 'x'));
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
2. 查看每一行的物理位置
SQL> select id,
2 dbms_rowid.rowid_object(rowid) obj#,
3 dbms_rowid.rowid_relative_fno(rowid) file#,
4 dbms_rowid.rowid_block_number(rowid) block#,
5 dbms_rowid.rowid_row_number(rowid) row#
6 from t_rowid
7 where rownum <= 5;
ID OBJ# FILE# BLOCK# ROW#
---------- ---------- ---------- ---------- ----------
1 74598 5 178 0
2 74598 5 178 1
3 74598 5 178 2
4 74598 5 178 3
5 74598 5 178 4
同一块内的行块号相同、行槽号递增,规律一目了然。
3. 在 PL/SQL 中用 ROWID_INFO 一次取全
SQL> set serveroutput on
SQL> declare
2 v_type number; v_obj number; v_fno number; v_bno number; v_rno number;
3 begin
4 dbms_rowid.rowid_info(
5 (select max(rowid) from t_rowid),
6 v_type, v_obj, v_fno, v_bno, v_rno);
7 dbms_output.put_line('type=' || v_type || ' obj=' || v_obj ||
8 ' file=' || v_fno || ' block=' || v_bno ||
9 ' row=' || v_rno);
10 end;
11 /
type=1 obj=74598 file=5 block=326 row=41
PL/SQL procedure successfully completed.
4. 反向构造,验证往返一致
SQL> select count(*) from t_rowid
2 where rowid = dbms_rowid.rowid_create(1,
3 dbms_rowid.rowid_object(rowid),
4 dbms_rowid.rowid_relative_fno(rowid),
5 dbms_rowid.rowid_block_number(rowid),
6 dbms_rowid.rowid_row_number(rowid));
COUNT(*)
----------
10000
分解后再构造,仍能匹配回全部 10000 行,说明换算是无损的。 ROWID_CREATE 的第一个参数 1 即扩展 ROWID ;官方文档明确说明该函数用于测试目的,构造出的 ROWID 若指向不存在的位置,查询时可能返回空或报 ORA-1410 (非法 ROWID )、 ORA-28516 (块超出文件末尾)。
三、实测:统计每个块的行数
结合 GROUP BY,可以观察数据在块上的分布: SQL> select * from ( 2 select dbms_rowid.rowid_relative_fno(rowid) file#, 3 dbms_rowid.rowid_block_number(rowid) block#, 4 count(*) rows_in_block 5 from t_rowid 6 group by dbms_rowid.rowid_relative_fno(rowid), 7 dbms_rowid.rowid_block_number(rowid) 8 order by count(*) desc 9 ) where rownum <= 5; FILE# BLOCK# ROWS_IN_BLOCK ---------- ---------- ------------- 5 178 66 5 179 66 5 180 66 5 181 66 5 182 66
如果怀疑热点块竞争( V$SEGMENT_STATISTICS 中某段的 BUFFER_BUSY_WAITS 偏高),这个查询可以进一步定位竞争集中在哪些块、这些块上是什么数据。反过来,对已知的行执行分解,也能得到它所在的块。
四、实测:从等待事件定位对象
演示从 db file sequential read 的参数走完整流程。先找到等待中的会话(DBA): SQL> select sid, event, p1, p2 2 from v$session_wait 3 where event = 'db file sequential read'; SID EVENT P1 P2 ---------- -------------------- ---------- ---------- 210 db file sequential read 5 326 P1 为文件号、P2 为块号,用 DBA_EXTENTS 反查该块所属的段: SQL> select owner, segment_name, segment_type, tablespace_name 2 from dba_extents 3 where file_id = 5 4 and 326 between block_id and block_id + blocks - 1; OWNER SEGMENT_NAME SEGMENT_TYPE TABLESPACE_NAME ------ --------------- ------------------ -------------------- TEST T_ROWID TABLE USERS 到这里物理地址就落到了具体对象。若还想知道块里存了哪些行,可以在该表上按块范围扫描: SQL> select id from t_rowid 2 where dbms_rowid.rowid_relative_fno(rowid) = 5 3 and dbms_rowid.rowid_block_number(rowid) = 326; ID ---------- 9953 9954 9955 ... 也可以用 ROWID_CREATE 构造出指定块内某个行槽的 ROWID 后直接按 ROWID 访问,思路相同。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3182199/ ,如需转载,请注明出处,否则将追究法律责任。