文章目录
- 一点题外话
- 进入正题
- 查询segment header信息
- 检测记录数
- 对象分析
- 获取行数据(IOT LEAF)信息
- 导出行数据(IOT LEAF)
- 读取行数据
- 行内数据解析
- 行外数据解析
- 获取二级索引数据信息
- 导出二级索引数据
- 多级索引情况
- 再思考一下
- 聊聊数据顺序维护
- 总结
对于一张表的数据,数据库时是如何寻址并读取到数据的呢,针对DM8默认的IOT表来看看
一点题外话
PK_WITH_CLUSTER = 0
这个参数决定我们的PK是作为聚集主键来替代了ROWID作为数据组织标准,亦或是作为一个普通二级索引
为0时表按ROWID顺序进行聚集,PK作为二级索引,其中记录KEY和ROWID进行关联
为1时表按PK键值进行聚集,PK本身即为聚集主键,其他二级索引中记录KEY和PK KEY来进行关联
进入正题
测试程序原型
[root@dm8host7 demo]# ./dm_dpi_full
connect to server success!
create table dpitest(userid integer,uname varchar2(100),udate date,udatetime timestamp(6),ufloat float,udecimal number(12,4),utext long,uclob clob,ubyte bfile,ublob blob,constraint dpitest_pk primary key(userid))
create or replace directory KNIGHTDIR as '/home/DM8_NEW/drivers/dpi/demo';
insert into dpitest(userid,uname,udate,udatetime,ufloat,udecimal,utext,uclob,ubyte,ublob) values(?,?,?,?,?,?,?,?,?,?)
Rows Affected:1
select userid,uname,udate,udatetime,ufloat,udecimal,utext,uclob,ubyte,ublob from dpitest
Column Number:10
Name:USERID Type:7 Name:USERID Length:10 Scale:0 Nullable:0
Name:UNAME Type:2 Name:UNAME Length:100 Scale:0 Nullable:1
Name:UDATE Type:14 Name:UDATE Length:10 Scale:0 Nullable:1
Name:UDATETIME Type:16 Name:UDATETIME Length:26 Scale:6 Nullable:1
Name:UFLOAT Type:11 Name:UFLOAT Length:53 Scale:0 Nullable:1
Name:UDECIMAL Type:9 Name:UDECIMAL Length:12 Scale:4 Nullable:1
Name:UTEXT Type:19 Name:UTEXT Length:2147483647 Scale:0 Nullable:1
Name:UCLOB Type:19 Name:UCLOB Length:2147483647 Scale:0 Nullable:1
Name:UBYTE Type:1000 Name:UBYTE Length:512 Scale:0 Nullable:1
Name:UBLOB Type:12 Name:UBLOB Length:2147483647 Scale:0 Nullable:1
666
卡布达
1986-10-7
1986-10-7 12:12:43.423000
412.309998
31412.311000
Row Count:1
查询segment header信息
要找到数据的物理位置,首先需要确定其segment header位置,
SQL> select b.id TBSID,a.HEADER_FILE,a.HEADER_BLOCK
from dba_segments a,v$tablespace b
where segment_name = 'DPITEST'
and a.tablespace_name=b.name;
行号 TBSID HEADER_FILE HEADER_BLOCK
---------- ----------- ----------- ------------
1 4 0 336
检测记录数
该表的segment header存储在 4号TBS, 0号FILE 的PAGE 336中
可以通过DBMS_PAGE这个Package来查看一下该PAGE的信息,这里我将向后一定范围的页一起做了检测
DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
PN int;
BASEN int = 336; //起始地址
BEGIN
for i in 0..100 loop //检测数量
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(4,0,BASEN+i,HEAD);
print 'PAGE_NO:'||BASEN+i||' RECORD NUMBER:'||HEAD.N_REC;
exception
when OTHERS then NULL;
end;
end loop;
END;
PAGE_NO:336 RECORD NUMBER:1
PAGE_NO:337 RECORD NUMBER:0
PAGE_NO:352 RECORD NUMBER:1
PAGE_NO:353 RECORD NUMBER:0
DMSQL 过程已成功完成
已用时间: 1.154(毫秒). 执行号:1404.
对象分析
由于测试库是空库,实际上我们找到了2个对象都和之前的操作有关,那么具体是什么呢
SQL> select OBJECT_ID,INDEX_NAME,INDEX_TYPE,OBJECT_TYPE,INDEX_TYPE,UNIQUENESS from dba_indexes a,dba_objects b where TABLE_NAME='DPITEST' and a.INDEX_NAME=B.OBJECT_NAME;
行号 OBJECT_ID INDEX_NAME INDEX_TYPE OBJECT_TYPE INDEX_TYPE UNIQUENESS
---------- --------- ------------- ---------- ----------- ---------- ----------
1 33555579 INDEX33555839 NORMAL INDEX NORMAL UNIQUE
2 33555578 INDEX33555838 CLUSTER INDEX CLUSTER NONUNIQUE
已用时间: 50.359(毫秒). 执行号:76701.
SQL> select * from v$segmentinfo where index_id=33555578
union all
select * from v$segmentinfo where index_id=335555792 3 ;
行号 INDEX_ID SEG_ID LOGIC_PAGE_NO PHY_PAGE_NO PHY_FIRST_PAGE_IN_EXTENT FIL_ID USE_BYTES FREE_BYTES
---------- ----------- ----------- ------------- ----------- ------------------------ ----------- -------------------- --------------------
1 33555578 2415 0 336 336 0 260 7932
2 33555579 2417 0 352 352 0 131 8061
已用时间: 1.242(毫秒). 执行号:1407.
由于表数据本身其实即为IOT的LEAF,所以上面检测到的对象一个是CLUSTER INDEX,另一个INDEX是我建立PK带入的二级索引
获取行数据(IOT LEAF)信息
查看具体数据在页面内部存放的偏移量和长度
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 336;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..10 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
PAGE_NO:336 RECORD NUMBER:1
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
1 1 98 148 0 22108 1 NULL NULL NULL
已用时间: 1.714(毫秒). 执行号:1414.
这里通过数据页头,就可以根据SLOT_NO获取到实际数据起始位置和长度信息了
导出行数据(IOT LEAF)
直接dd出来看具体内容
[root@dm8host7 /]# dd if=/home/DM8/data/DAMENG/MAIN.DBF of=/tmp/test.tmp bs=8192 skip=336 count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 0.000169785 s, 48.2 MB/s
此处不考虑平台字字节序问题,Linux默认小端,通过-C来统一输出得到如下结果
[root@dm8host7 tmp]# hexdump -C test.tmp
00000000 04 00 00 00 50 01 00 00 ff ff ff ff ff ff ff ff |....P...........|
00000010 ff ff ff ff 14 00 00 00 00 00 00 00 a9 57 01 00 |.............W..|
00000020 00 00 00 00 03 00 f6 00 00 00 00 00 01 00 ff ff |................|
00000030 52 00 5a 00 00 00 04 01 00 00 7a 04 00 02 04 00 |R.Z.......z.....|
00000040 00 00 08 00 00 00 c4 09 04 00 00 00 08 00 00 00 |................|
00000050 7c 09 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ||...............|
00000060 ff ff 00 94 00 00 00 9a 02 00 00 6c 87 08 6c 87 |...........l..l.|
00000070 08 77 5f 4f 03 00 ae 47 e1 7a 14 ae f3 3f 89 e5 |.w_O...G.z...?..|
00000080 8d a1 e5 b8 83 e8 be be 86 c3 04 0f 0d 20 0b 95 |............. ..|
00000090 02 47 02 00 00 00 00 00 00 dc 09 00 00 04 00 00 |.G..............|
000000a0 00 46 01 00 00 95 02 49 02 00 00 00 00 00 00 7f |.F.....I........|
000000b0 3a 0a 00 04 00 00 00 47 01 00 00 91 4b 4e 49 47 |:......G....KNIG|
000000c0 48 54 44 49 52 3a 63 61 74 2e 6a 70 67 95 02 4b |HTDIR:cat.jpg..K|
000000d0 02 00 00 00 00 00 00 ed bf 00 00 04 00 00 00 bc |................|
000000e0 01 00 00 01 00 00 00 00 00 00 ef 02 00 00 c9 02 |................|
000000f0 be 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |.W..............|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001ff0 00 00 5a 00 62 00 52 00 00 00 00 00 00 00 00 00 |..Z.b.R.........|
00002000
读取行数据
页的尾部会存储偏移量数组,用于定位SLOT的OFFSET,比如我这里只有一行,是从第98个字节开始,在行开始的第一个字节为删除标记,接下来两个字节会记录本行数据总长度,比如我这里为148,之后是依次记录的数据信息,最后还会包含ROWID ROLLPTR TRXID等信息,比如我这里的 trxid为22462,具体可以参考我的另一篇关于数据页格式的博文
行内数据解析
根据起始位置offset 98 换算为16进制的地址0x00000062和长度148字节,就可以得到完整的磁盘数据,对于部分字段类型解析方法可以参考我的另一篇关于数据类型表达格式的博文,在此也就不重复说明了
行外数据解析
对于LOB等外部存储的类型,会通过行内记录的元数据信息来进行二次读取,这里以数据中一个TEXT/LONG类型为例简单说明
从上面的dd数据种可以看到该字段的数据16进制存储为
02 47 02 00 00 00 00 00 00 dc 09 00 00 04 00 00 00 46 01 00 00
95 02 49 02 00 00 00 00 00 00 未知 与 dump数据不对应,可能为压缩存储格式
利用这些已知信息就可以定位到LOB数据内容
[root@dm8host7 tmp]# dd if=/home/DM8/data/DAMENG/MAIN.DBF of=/tmp/test.tmp bs=8192 skip=326 count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 0.000294614 s, 27.8 MB/s
[root@dm8host7 tmp]# hexdump -C test.tmp
00000000 04 00 00 00 46 01 00 00 00 00 41 01 00 00 ff ff |....F.....A.....|
00000010 ff ff ff ff 22 00 00 00 00 00 00 00 bc 0d 01 00 |...."...........|
00000020 00 00 00 00 03 00 5a 1a 00 00 00 00 01 00 4c 0a |......Z.......L.|
00000030 52 00 5a 00 00 00 5a 0a 00 00 00 00 00 00 00 00 |R.Z...Z.........|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ff ff |................|
00000060 ff ff 09 ea 47 02 00 00 00 00 00 00 dc 09 dc 09 |....G...........|
00000070 72 6f 6f 74 3a 78 3a 30 3a 30 3a 72 6f 6f 74 3a |root:x:0:0:root:|
00000080 2f 72 6f 6f 74 3a 2f 62 69 6e 2f 62 61 73 68 0a |/root:/bin/bash.|
00000090 62 69 6e 3a 78 3a 31 3a 31 3a 62 69 6e 3a 2f 62 |bin:x:1:1:bin:/b|
000000a0 69 6e 3a 2f 73 62 69 6e 2f 6e 6f 6c 6f 67 69 6e |in:/sbin/nologin|
000000b0 0a 64 61 65 6d 6f 6e 3a 78 3a 32 3a 32 3a 64 61 |.daemon:x:2:2:da|
000000c0 65 6d 6f 6e 3a 2f 73 62 69 6e 3a 2f 73 62 69 6e |emon:/sbin:/sbin|
000000d0 2f 6e 6f 6c 6f 67 69 6e 0a 61 64 6d 3a 78 3a 33 |/nologin.adm:x:3|
000000e0 3a 34 3a 61 64 6d 3a 2f 76 61 72 2f 61 64 6d 3a |:4:adm:/var/adm:|
000000f0 2f 73 62 69 6e 2f 6e 6f 6c 6f 67 69 6e 0a 6c 70 |/sbin/nologin.lp|
00000100 3a 78 3a 34 3a 37 3a 6c 70 3a 2f 76 61 72 2f 73
......
到此如果我们只是全LEAF扫描,这样就可以得到数据了,那么如果是通过二级索引INDEX KEY来扫描呢
获取二级索引数据信息
DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 352;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..10 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
1 1 98 19 0 22108 NULL 0 1 0
已用时间: 1.246(毫秒). 执行号:1612.
导出二级索引数据
导出索引页中数据
[root@dm8host7 tmp]# dd if=/home/DM8/data/DAMENG/MAIN.DBF of=/tmp/test.tmp bs=8192 skip=352 count=1
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 0.00032188 s, 25.5 MB/s
[root@dm8host7 tmp]# hexdump -C test.tmp
00000000 04 00 00 00 60 01 00 00 ff ff ff ff ff ff ff ff |....`...........|
00000010 ff ff ff ff 16 00 00 00 00 00 00 00 62 17 01 00 |............b...|
00000020 00 00 00 00 03 00 75 00 00 00 00 00 01 00 ff ff |......u.........|
00000030 52 00 5a 00 00 00 83 00 00 00 7b 04 00 02 04 00 |R.Z.......{.....|
00000040 00 00 08 00 00 00 54 0a 04 00 00 00 08 00 00 00 |......T.........|
00000050 0c 0a 00 00 00 00 00 00 00 00 ff ff ff ff ff ff |................|
00000060 ff ff 00 13 00 9a 02 00 00 01 00 00 00 00 00 5c |...............\|
00000070 56 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |V...............|
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001ff0 00 00 5a 00 62 00 52 00 00 00 00 00 00 00 00 00 |..Z.b.R.........|
00002000
偏移量98开始 19个字节
13 00 9a 02 00 00 01 00 00 00 00 00 5c 56 00 00 00 00 00
9a 02 00 00 KEY值 666
01 00 00 00 逻辑ROWID 1
56 5c TRXID 22108 用于MVCC可见性处理
二级索引中存储了自身的KEY值和表的逻辑ROWID,用于定位实际的数据行,这样就可以对应到表数据行的CLU_ROWID来找到对应行物理位置了
多级索引情况
当我们数据较多,索引分裂出现多个节点的时候,情况会变得稍微有些复杂,这里我们不必再考虑数据的问题,仅测试寻址的变化
drop table test;
create table test(id int,name varchar(100),primary key(id))
begin
for i in 100..1000
loop
insert into test values(i,'test');
end loop;
end;
select checkpoint(100)
此时我们来观察索引的情况
SQL> select OBJECT_ID,INDEX_NAME,INDEX_TYPE,OBJECT_TYPE,INDEX_TYPE,UNIQUENESS
from dba_indexes a,dba_objects b
where TABLE_NAME='TEST' and a.INDEX_NAME=B.OBJECT_NAME;
行号 OBJECT_ID INDEX_NAME INDEX_TYPE OBJECT_TYPE INDEX_TYPE UNIQUENESS
---------- --------- ------------- ---------- ----------- ---------- ----------
1 33555845 INDEX33555845 NORMAL INDEX NORMAL UNIQUE
2 33555844 INDEX33555844 CLUSTER INDEX CLUSTER NONUNIQUE
已用时间: 235.830(毫秒). 执行号:76706.
此时索引将会出现多个页
SQL> select * from v$segmentinfo where index_id=33555844;
行号 INDEX_ID SEG_ID LOGIC_PAGE_NO PHY_PAGE_NO PHY_FIRST_PAGE_IN_EXTENT FIL_ID USE_BYTES FREE_BYTES
---------- ----------- ----------- ------------- ----------- ------------------------ ----------- -------------------- --------------------
1 33555844 2922 0 7232 7232 0 8162 30
2 33555844 2922 1 7265 7264 0 8162 30
3 33555844 2922 2 7266 7264 0 8162 30
4 33555844 2922 3 7267 7264 0 5687 2505
已用时间: 0.968(毫秒). 执行号:76707.
而且有趣的是第一个页与后续是不连续的,分别观察一下这几个页的内容
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7232;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..10 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
1 1 7631 15 0 245 NULL NULL NULL NULL
2 2 7600 15 0 489 NULL NULL NULL NULL
3 3 7569 15 0 733 NULL NULL NULL NULL
已用时间: 38.674(毫秒). 执行号:76708.
这个页里CLU_ROWID为NULL了,只有3行内容,但是TRX_ID变了,同一个TRX操作确产生不同TRX_ID,并且SLOT_NO按OFFSET逆序进行记录,先来看看后面3个页
首先看看7265
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7265;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
1 1 98 31 0 423172 245 NULL NULL NULL
2 2 129 31 0 423172 246 NULL NULL NULL
3 3 160 31 0 423172 247 NULL NULL NULL
......
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
232 232 7259 31 0 423172 476 NULL NULL NULL
233 233 7290 31 0 423172 477 NULL NULL NULL
234 234 7321 31 0 423172 478 NULL NULL NULL
235 235 7352 31 0 423172 479 NULL NULL NULL
236 236 7383 31 0 423172 480 NULL NULL NULL
237 237 7414 31 0 423172 481 NULL NULL NULL
238 238 7445 31 0 423172 482 NULL NULL NULL
239 239 7476 31 0 423172 483 NULL NULL NULL
240 240 7507 31 0 423172 484 NULL NULL NULL
241 241 7538 31 0 423172 485 NULL NULL NULL
242 242 7569 31 0 423172 486 NULL NULL NULL
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
243 243 7600 31 0 423172 487 NULL NULL NULL
244 244 7631 31 0 423172 488 NULL NULL NULL
244 rows got
已用时间: 1.896(毫秒). 执行号:168200.
再看看7266
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7266;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
......
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
232 232 7259 31 0 423172 720 NULL NULL NULL
233 233 7290 31 0 423172 721 NULL NULL NULL
234 234 7321 31 0 423172 722 NULL NULL NULL
235 235 7352 31 0 423172 723 NULL NULL NULL
236 236 7383 31 0 423172 724 NULL NULL NULL
237 237 7414 31 0 423172 725 NULL NULL NULL
238 238 7445 31 0 423172 726 NULL NULL NULL
239 239 7476 31 0 423172 727 NULL NULL NULL
240 240 7507 31 0 423172 728 NULL NULL NULL
241 241 7538 31 0 423172 729 NULL NULL NULL
242 242 7569 31 0 423172 730 NULL NULL NULL
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
243 243 7600 31 0 423172 731 NULL NULL NULL
244 244 7631 31 0 423172 732 NULL NULL NULL
244 rows got
已用时间: 1.813(毫秒). 执行号:168201.
最后是7267
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7266;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
......
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
144 144 4531 31 0 423172 876 NULL NULL NULL
145 145 4562 31 0 423172 877 NULL NULL NULL
146 146 4593 31 0 423172 878 NULL NULL NULL
147 147 4624 31 0 423172 879 NULL NULL NULL
148 148 4655 31 0 423172 880 NULL NULL NULL
149 149 4686 31 0 423172 881 NULL NULL NULL
150 150 4717 31 0 423172 882 NULL NULL NULL
151 151 4748 31 0 423172 883 NULL NULL NULL
152 152 4779 31 0 423172 884 NULL NULL NULL
153 153 4810 31 0 423172 885 NULL NULL NULL
154 154 4841 31 0 423172 886 NULL NULL NULL
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
155 155 4872 31 0 423172 887 NULL NULL NULL
156 156 4903 31 0 423172 888 NULL NULL NULL
157 157 4934 31 0 423172 889 NULL NULL NULL
158 158 4965 31 0 423172 890 NULL NULL NULL
159 159 4996 31 0 423172 891 NULL NULL NULL
160 160 5027 31 0 423172 892 NULL NULL NULL
161 161 5058 31 0 423172 893 NULL NULL NULL
162 162 5089 31 0 423172 894 NULL NULL NULL
163 163 5120 31 0 423172 895 NULL NULL NULL
164 164 5151 31 0 423172 896 NULL NULL NULL
165 165 5182 31 0 423172 897 NULL NULL NULL
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
166 166 5213 31 0 423172 898 NULL NULL NULL
167 167 5244 31 0 423172 899 NULL NULL NULL
168 168 5275 31 0 423172 900 NULL NULL NULL
169 169 5306 31 0 423172 901 NULL NULL NULL
169 rows got
已用时间: 2.561(毫秒). 执行号:168202.
后面的3个页中我们找到了CLU_ROWID,这样情况就和未分裂之前相同了,那么第一个索引页末尾那3行是什么呢,实际上那个TRX_ID记录的是该页中ROWID的下限值,具体分析过程比较繁琐,无非是一些解物理存储进行比对,在此不再赘述。
再思考一下
回到此前的问题,ROWID小于245的部分又去哪了呢?
换个思路,MySQL底层存储思路也是通过记录下限值来划分边界,这就意味着最小值应该是-1,这是不是意味着我们看到的记录的第一个实际数据页之前就是记录所有低于下限值的数据页呢
[root@dm8host7 demo]# dd if=/home/DM8/data/DAMENG/MAIN.DBF of=/tmp/test.dbf bs=8192 skip=7264 count=1
[root@dm8host7 demo]# hexdump -C /tmp/test.dbf |more
00000000 04 00 00 00 60 1c 00 00 ff ff ff ff ff ff 00 00 |....`...........|
00000010 61 1c 00 00 14 00 00 00 00 00 00 00 fa 8f ab 00 |a...............|
00000020 00 00 00 00 f6 00 ee 1d 00 00 00 00 f4 00 ff ff |................|
00000030 52 00 5a 00 00 00 e2 1f 00 00 84 05 00 02 00 00 |R.Z.............|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ff ff |................|
00000060 ff ff 00 1f 00 64 00 00 00 84 74 65 73 74 01 00 |.....d....test..|
00000070 00 00 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 |............u...|
00000080 00 00 1f 00 65 00 00 00 84 74 65 73 74 02 00 00 |....e....test...|
00000090 00 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 |...........u....|
000000a0 00 1f 00 66 00 00 00 84 74 65 73 74 03 00 00 00 |...f....test....|
000000b0 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 00 |..........u.....|
000000c0 1f 00 67 00 00 00 84 74 65 73 74 04 00 00 00 00 |..g....test.....|
000000d0 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 00 1f |.........u......|
页头的部分是固定的98,那么来看看这之后放了些啥
00 1f 00 64 00 00 00 84 74 65 73 74 01 00 |.....d....test..|
00000070 00 00 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 |............u...|
00000080 00 00 1f 00 65 00 00 00 84 74 65 73 74 02 00 00 |....e....test...|
00000090 00 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 |...........u....|
000000a0 00 1f 00 66 00 00 00 84 74 65 73 74 03 00 00 00 |...f....test....|
000000b0 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 00 |..........u.....|
000000c0 1f 00 67 00 00 00 84 74 65 73 74 04 00 00 00 00 |..g....test.....|
000000d0 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 00 1f |.........u......|
由于数据较多,我这里只提取前几行,后面都是类似的内容。
是的,这便是从ROWID 1开始的行数据,为了保险起见我们再验证一下末尾最后一行数据记录的是啥
00001db0 00 1f 00 56 01 00 00 84 74 65 73 74 f3 00 00 00 |...V....test....|
00001dc0 00 00 ff ff ff ff 7f ff ff 04 75 06 00 00 00 00 |..........u.....|
00001dd0 1f 00 57 01 00 00 84 74 65 73 74 f4 00 00 00 00 |..W....test.....|
00001de0 00 ff ff ff ff 7f ff ff 04 75 06
正好这里的最后一行ROWID是244,这就完美对应上了,同时我们也可以从头部信息得到反向佐证
DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7264;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
......
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
232 232 7259 31 0 423172 232 NULL NULL NULL
233 233 7290 31 0 423172 233 NULL NULL NULL
234 234 7321 31 0 423172 234 NULL NULL NULL
235 235 7352 31 0 423172 235 NULL NULL NULL
236 236 7383 31 0 423172 236 NULL NULL NULL
237 237 7414 31 0 423172 237 NULL NULL NULL
238 238 7445 31 0 423172 238 NULL NULL NULL
239 239 7476 31 0 423172 239 NULL NULL NULL
240 240 7507 31 0 423172 240 NULL NULL NULL
241 241 7538 31 0 423172 241 NULL NULL NULL
242 242 7569 31 0 423172 242 NULL NULL NULL
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
243 243 7600 31 0 423172 243 NULL NULL NULL
244 244 7631 31 0 423172 244 NULL NULL NULL
244 rows got
已用时间: 2.539(毫秒). 执行号:181701.
聊聊数据顺序维护
这里我建立一个按CLUSTER PK组织的表,并刻意插入反序的int和varchar数值,此时磁盘数据如下
00000000 04 00 00 00 10 1d 00 00 ff ff ff ff ff ff ff ff |................|
00000010 ff ff ff ff 14 00 00 00 00 00 00 00 f1 39 ac 00 |.............9..|
00000020 00 00 00 00 07 00 ee 00 00 00 00 00 05 00 ff ff |................|
00000030 52 00 5a 00 00 00 04 01 00 00 86 05 00 02 04 00 |R.Z.............|
00000040 00 00 01 08 00 00 34 09 04 00 00 00 01 08 00 00 |......4.........|
00000050 ec 08 00 00 00 00 00 00 00 00 ff ff ff ff ff ff |................|
00000060 ff ff 00 1c 00 0a 00 00 00 81 7a 01 00 00 00 00 |..........z.....|
00000070 00 ff ff ff ff 7f ff ff 90 d2 06 00 00 00 00 1c |................|
00000080 00 09 00 00 00 81 79 02 00 00 00 00 00 ff ff ff |......y.........|
00000090 ff 7f ff ff 92 d2 06 00 00 00 00 1c 00 08 00 00 |................|
000000a0 00 81 78 03 00 00 00 00 00 ff ff ff ff 7f ff ff |..x.............|
000000b0 93 d2 06 00 00 00 00 1c 00 07 00 00 00 81 72 04 |..............r.|
000000c0 00 00 00 00 00 ff ff ff ff 7f ff ff 94 d2 06 00 |................|
000000d0 00 00 00 1c 00 06 00 00 00 81 61 05 00 00 00 00 |..........a.....|
000000e0 00 ff ff ff ff 7f ff ff 95 d2 06 00 00 00 00 00 |................|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001fe0 00 00 00 00 00 00 00 00 00 00 5a 00 62 00 7e 00 |..........Z.b.~.|
00001ff0 9a 00 b6 00 d2 00 52 00 00 00 00 00 00 00 00 00 |......R.........|
二级索引数据如下
[root@dm8host7 ~]# hexdump -C /tmp/test1_idx.dbf
00000000 04 00 00 00 20 1d 00 00 ff ff ff ff ff ff ff ff |.... ...........|
00000010 ff ff ff ff 16 00 00 00 00 00 00 00 f2 39 ac 00 |.............9..|
00000020 00 00 00 00 07 00 cb 00 00 00 00 00 05 00 ff ff |................|
00000030 52 00 5a 00 00 00 e1 00 00 00 88 05 00 02 04 00 |R.Z.............|
00000040 00 00 01 08 00 00 c4 09 04 00 00 00 01 08 00 00 |................|
00000050 7c 09 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ||...............|
00000060 ff ff 00 15 00 0a 00 00 00 81 7a 01 00 00 00 00 |..........z.....|
00000070 00 90 d2 06 00 00 00 00 15 00 09 00 00 00 81 79 |...............y|
00000080 02 00 00 00 00 00 92 d2 06 00 00 00 00 15 00 08 |................|
00000090 00 00 00 81 78 03 00 00 00 00 00 93 d2 06 00 00 |....x...........|
000000a0 00 00 15 00 07 00 00 00 81 72 04 00 00 00 00 00 |.........r......|
000000b0 94 d2 06 00 00 00 00 15 00 06 00 00 00 81 61 05 |..............a.|
000000c0 00 00 00 00 00 95 d2 06 00 00 00 00 00 00 00 00 |................|
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001fe0 00 00 00 00 00 00 00 00 00 00 5a 00 62 00 77 00 |..........Z.b.w.|
00001ff0 8c 00 a1 00 b6 00 52 00 00 00 00 00 00 00 00 00 |......R.........|
00002000
相关数据看起来和我插入顺序一模一样,也并没有针对我要的顺序排序,这样似乎不太合理,那么继续看数据页和索引页的页头
数据页
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7440;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
print 'PA2 3 4 5 6 7 8 9 10 11 12 13 GE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELE14 15 16 17 18 CT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;19 20 21 22 23 24 25
26 /
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
---------- ----------- ----------- ----------- ----------- -------------------- -------------------- -------------- -------------- -------------
1 1 210 28 0 447125 5 NULL NULL NULL
2 2 182 28 0 447124 4 NULL NULL NULL
3 3 154 28 0 447123 3 NULL NULL NULL
4 4 126 28 0 447122 2 NULL NULL NULL
5 5 98 28 0 447120 1 NULL NULL NULL
已用时间: 18.704(毫秒). 执行号:198400.
索引页
SQL> DECLARE
HEAD DBMS_PAGE.DPH_T;
INFO dbms_page.REC_ARR_T;
BASN int = 7456;
TBSN int = 4;
FILN int = 0;
BEGIN
for i in 0..1 loop
begin
DBMS_PAGE.DATA_PAGE_HEAD_LOAD(TBSN,FILN,BASN,HEAD);
if(HEAD.N_REC)>0 then
a[i] = BASN;
print 'PAGE_NO:'||BASN||' RECORD NUMBER:'||HEAD.N_REC;
INFO = NEW dbms_page.REC_T[HEAD.N_REC];
FOR J IN 1..HEAD.N_REC LOOP
DBMS_PAGE.DATA_PAGE_REC_BY_SLOT_NO_LOAD(TBSN ,FILN,BASN,J,INFO[J]);
END LOOP;
SELECT * FROM ARRAY INFO;
end if;
exception
when OTHERS then NULL;
end;
BASN = BASN +i;
end loop;
END;
/
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID
---------- ----------- ----------- ----------- ----------- --------------------
CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
-------------------- -------------- -------------- -------------
1 1 182 21 0 447125
NULL 97 5 0
2 2 161 21 0 447124
NULL 114 4 0
3 3 140 21 0 447123
NULL 120 3 0
行号 SLOT_NO OFFSET LEN IS_DEL TRX_ID
---------- ----------- ----------- ----------- ----------- --------------------
CLU_ROWID ROLL_ADDR_FILE ROLL_ADDR_PAGE ROLL_ADDR_OFF
-------------------- -------------- -------------- -------------
4 4 119 21 0 447122
NULL 121 2 0
5 5 98 21 0 447120
NULL 122 1 0
已用时间: 1.258(毫秒). 执行号:195700.
实际上这里通过SLOT_NO正序和磁盘尾部OFFSET逆序组合来满足了我们索引排序的顺序,而避免了在存储时重新排序数据的操作,这样设计在IO时应当能够有效消除额外排序动作的开销
总结
到此就完成了一个最简单的寻址测试,实际使用场景还需要考量更多的机制和问题,欢迎了解的同学共同分享。