介绍
对于一张表的数据,数据库是如何寻址并读取到其真实的数据,这便是寻址方式, 寻找到表数据的物理地址后dump出相关的内容。
一、模拟数据
CREATE TABLESPACE "DMUPUSER.DBF" DATAFILE 'DMUPUSER.DBF' SIZE 128 CACHE = NORMAL;
create user "DMUPUSER" identified by "123456789"
default tablespace "DMUPUSER.DBF"
default index tablespace "DMUPUSER.DBF";
grant "PUBLIC","SOI" to "DMUPUSER";
---建表测试表PAGETEST、插入数据
create table "DMUPUSER"."PAGETEST"
(
"C1" INT,
"C2" VARCHAR(50),
"C3" INT
)
storage(initial 1, next 1, minextents 1, fillfactor 0, on "DMUPUSER.DBF")
;
-- 建表PAGETEST_DEL并插入数据在删除
create table "DMUPUSER"."PAGETEST_DEL"
(
"C1" INT,
"C2" VARCHAR(50),
"C3" INT
)
storage(initial 1, next 1, minextents 1, fillfactor 0, on "DMUPUSER.DBF")
;
INSERT INTO "DMUPUSER"."PAGETEST_DEL" SELECT LEVEL,LEVEL,LEVEL FROM DUAL CONNECT BY LEVEL <1000;
DROP TABLE "DMUPUSER"."PAGETEST_DEL";
INSERT INTO "DMUPUSER"."PAGETEST" SELECT LEVEL,LEVEL,LEVEL FROM DUAL CONNECT BY LEVEL <100000000;
commit;
select checkpoint(100);
二、查找表的物理地址并导出数据页
- 达梦的表是索引组织表(Index-Organized Table),主要特点是按照一个主键值的顺序进行存储,同时支持基于范围的查询。
- 在达梦中表(列存储表和堆表除外)都是使用 B 树索引结构管理的,每一个普通表都有一个聚集索引,数据通过聚集索引键排序,根据聚集索引键可以快速查询任何记录。
- 因导出表的数据页,需要先找到表在数据文件中物理地址。
- 寻找表的物理地址有两个思路,第一个通过系统视图SYSOBJECTS表找到表的根页号随后导出表的数据页信息。第二个是找到表的聚集索引的ID,然后查找该索引的所有段信息随后dump出数据页信息。
- 采用方法二的话,可以从表的聚集索引中看到该表用了多少个页,可根据实际需求单独导出表的某个页分析处理。
方法一、通过SYSOBJECTS表定位_(错误)
思路:主要是操作SYSOBJECTS表,该表记录系统中所有对象的信息,可以找到表的根页号从而导出表的数据页。这种方式导出数据页,可能是错误的。因为表在物理上不一定是连续的,也会出现跨段等情况。
- 因多个模式下可能存在相同名字的表,首先先获取模式对应的ID号
-- 以DMUPUSER用户为例
select ID from SYSOBJECTS where name = 'PAGETEST' AND TYPE$ = 'SCH';
- 获取模式ID下数据表的ID
-- 以DMUPUSER用户的PAGETEST表为例
SELECT ID FROM SYSOBJECTS WHERE NAME='PAGETEST' AND SCHID = (select ID from SYSOBJECTS where name = 'DMUPUSER' AND TYPE$ = 'SCH')
- 因达梦是索引组织表,根据表ID查询SYSINDEXES可获取表在文件中的偏移量
-- 以DMUPUSER用户的DMUP_TEST01表为例
SELECT *
FROM SYSINDEXES
WHERE ID =(SELECT ID
FROM SYSOBJECTS
WHERE PID=(SELECT ID
FROM SYSOBJECTS
WHERE NAME='PAGETEST'
AND SCHID = (select ID from SYSOBJECTS where name = 'DMUPUSER' AND TYPE$ = 'SCH'))
ORDER BY ID LIMIT 1)
SYSINDEXES表的字段介绍
- 根据GROUPID与ROOTFILE字段查询V$DATAFILE表 可以获取到数据是在哪个表空间下
select * from V$DATAFILE WHERE GROUPID = :GROUP_ID AND ID = :ROOTFILE
-- 以DMUPUSER用户的DMUP_TEST01表为例
select * , ( select CLIENT_PATH
from V$DATAFILE
WHERE GROUP_ID = t.GROUPID
AND ID = t.ROOTFILE) TABLESPACES_NAME
from ( SELECT *
FROM SYSINDEXES
WHERE ID =(SELECT ID
FROM SYSOBJECTS
WHERE PID=(SELECT ID
FROM SYSOBJECTS
WHERE NAME='PAGETEST'
AND SCHID = (select ID from SYSOBJECTS where name = 'DMUPUSER' AND TYPE$ = 'SCH'))
ORDER BY ID LIMIT 1)) t
查询结果说明:DMUPUSER用户的PAGETEST表在数据文件DMUPUSER.DBF中,数据的根页为16 (ROOTPAGE字段)。
- 根据表数据的根页号导出相关的数据页,使用如下命令并注意命令的值修改。
dd if=DMUPUSER.DBF of=test1.txt skip=`echo 16*8192|bc` ibs=1 count=8192;
注意:
- if 的值为数据所在的表空间文件也就是TABLESPACES_NAME字段
- skip 中的 16*8192 ,这里的16为查询dump表的页号(ROOTPAGE字段),8192表示数据库页的大小.(8k页=8192 32k页=32768)
- count参数为导出的块个数
dd 是 Linux 系统中的一个命令,用于进行数据转换和复制。
- if= 用于指定输入的文件(即 source,源文件)。
- of= 用于指定输出的文件(即 destination,目标文件)。
- skip= 用于指定从输入文件的哪个位置开始读取数据(即跳过前面的数据)。
- ibs= 用于指定每次读取多少个字节(即输入块大小,input block size)。这里表示每次读取一个字节。
- count= 用于指定要读取多少个块(即读取的块数)。8192 表示要读取 8192 个块,每个块大小为 1 个字节,即总共读取 8192 个字节。
- xdd命令或hexdump命令查看导出的数据页
## 1. 导出数据执行
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test1.txt skip=`echo 32*8192|bc` ibs=1 count=8192;
记录了8192+0 的读入
记录了16+0 的写出
8192字节(8.2 kB,8.0 KiB)已复制,0.00365255 s,2.2 MB/s
## 2. 使用xxd命令查看二进制文件,可以观察到insert的DUMP_STR可以在二进制中看到
[root@localhost DAMENG]# xxd test1.txt
00000000: 0b00 0000 2000 0000 ffff ffff ffff ffff .... ...........
00000010: ffff ffff 1500 0000 75c1 2341 abc4 f400 ........u.#A....
00000020: 0000 0000 0400 371c 0000 0000 0200 8000 ......7.........
00000030: 5200 5a00 0000 9000 0300 f70f 0002 0b00 R.Z.............
00000040: 0000 0800 0000 0c01 0b00 0000 0800 0000 ................
00000050: c400 4f43 0a00 0000 0000 ffff ffff ffff ..OC............
00000060: ffff 000f 0080 5d0a 0000 0091 d4ab 0200 ......].........
00000070: 0000 0f00 1cc3 0d00 0000 0e0e 5205 0000 ............R...
00000080: 000f 008f 000a 0000 00fc 8ba7 0200 0000 ................
00000090: 0f00 9e00 0a00 0000 751e a602 0000 000f ........u.......
000000a0: 00ad 000a 0000 00ee b0a4 0200 0000 0f00 ................
000000b0: bc00 0a00 0000 6743 a302 0000 000f 00cb ......gC........
000000c0: 000a 0000 00e0 d5a1 0200 0000 0f00 da00 ................
000000d0: 0a00 0000 5968 a002 0000 000f 00e9 000a ....Yh..........
000000e0: 0000 00d2 fa9e 0200 0000 0f00 f800 0a00 ................
000000f0: 0000 4b8d 9d02 0000 000f 0007 010a 0000 ..K.............
00000100: 00c4 1f9c 0200 0000 0f00 1601 0a00 0000 ................
00000110: 3db2 9a02 0000 000f 0025 010a 0000 00b6 =........%......
00000120: 4499 0200 0000 0f00 3401 0a00 0000 2fd7 D.......4...../.
00000130: 9702 0000 000f 0043 010a 0000 00a8 6996 .......C......i.
00000140: 0200 0000 0f00 5201 0a00 0000 21fc 9402 ......R.....!...
00000150: 0000 000f 0061 010a 0000 009a 8e93 0200 .....a..........
00000160: 0000 0f00 7001 0a00 0000 1321 9202 0000 ....p......!....
00000170: 000f 007f 010a 0000 008c b390 0200 0000 ................
00000180: 0f00 8e01 0a00 0000 0546 8f02 0000 000f .........F......
00000190: 009d 010a 0000 007e d88d 0200 0000 0f00 .......~........
000001a0: ac01 0a00 0000 f76a 8c02 0000 000f 00bb .......j........
000001b0: 010a 0000 0070 fd8a 0200 0000 0f00 ca01 .....p..........
000001c0: 0a00 0000 e98f 8902 0000 000f 00d9 010a ................
000001d0: 0000 0062 2288 0200 0000 0f00 e801 0a00 ...b"...........
000001e0: 0000 dbb4 8602 0000 000f 00f7 010a 0000 ................
000001f0: 0054 4785 0200 0000 0f00 0602 0a00 0000 .TG.............
00000200: cdd9 8302 0000 000f 0015 020a 0000 0046 ...............F
00000210: 6c82 0200 0000 0f00 2402 0a00 0000 bffe l.......$.......
00000220: 8002 0000 000f 0033 020a 0000 0038 917f .......3.....8..
00000230: 0200 0000 0f00 4202 0a00 0000 b123 7e02 ......B......#~.
00000240: 0000 000f 0051 020a 0000 002a b67c 0200 .....Q.....*.|..
方法二、通过表的聚集索引定位
思路:查询表的聚集索引的ID,通过v$segmentinfo表查看该索引ID所有的段ID以及物理页号。
- 找到要导出表的聚集索引ID
select OBJECT_ID,INDEX_NAME,INDEX_TYPE,OBJECT_TYPE,INDEX_TYPE,
UNIQUENESS from dba_indexes a,dba_objects b where TABLE_NAME='PAGETEST' and a.INDEX_NAME=B.OBJECT_NAME and b.owner = 'DMUPUSER' AND INDEX_TYPE = 'CLUSTER' AND INDEX_NAME LIKE 'INDEX%';
- 根据聚集索引ID在视图v$segmentinfo中找到对应的段ID以及物理页号
-- 以DMUPUSER用户的PAGETEST表为例
select * from v$segmentinfo where index_id=( select OBJECT_ID from dba_indexes a,dba_objects b where TABLE_NAME='PAGETEST' and a.INDEX_NAME=B.OBJECT_NAME and b.owner = 'DMUPUSER' AND INDEX_TYPE = 'CLUSTER' AND INDEX_NAME LIKE 'INDEX%' );
-- 如需要确认数据表所在的数据文件,可根据查询结果的FIL_ID字段查询V$DATAFILE表查看表空间名称
-- 批量生成dd命令的脚本请查看附录
查询结果说明
- DMUPUSER用户的PAGETEST表在数据文件DMUPUSER.DBF中,数据的根页为16 (ROOTPAGE字段)。
- 根据v$segmentinfo表可以看到该数据段中第一个页是16,根据字段PHY_FIRST_PAGE_IN_EXTENT与PHY_PAGE_NO可以看到数据存储的页号应该是96。
- 如果有导出所有页的需求就需要逐条单个dd命令导出了,根据PHY_PAGE_NO的结果集中看到规律,页号有连续的。也可以通过修改dd命令的count参数导出多个页。
- 根据表数据的根页号导出相关的数据页,使用如下命令并注意命令的值修改。
使用如下命令
dd if=DMUPUSER.DBF of=test1.txt skip=`echo 96*8192|bc` ibs=1 count=8192;
注意:
- if 的值为数据所在的表空间文件也就是TABLESPACES_NAME字段
- skip 中的 96*8192 这里的 96为查询dump表的页号(ROOTPAGE字段),8192表示数据库页的大小,8k页=8192 32k页=32768
- count参数为导出的块个数
dd 是 Linux 系统中的一个命令,用于进行数据转换和复制。
- if= 用于指定输入的文件(即 source,源文件)。
- of= 用于指定输出的文件(即 destination,目标文件)。
- skip= 用于指定从输入文件的哪个位置开始读取数据(即跳过前面的数据)。
- ibs= 用于指定每次读取多少个字节(即输入块大小,input block size)。这里表示每次读取一个字节。
- count= 用于指定要读取多少个块(即读取的块数)。8192 表示要读取 8192 个块,每个块大小为 1 个字节,即总共读取 8192 个字节。
- xdd命令或hexdump命令查看导出的数据页
## 1. 导出数据执行
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test1.txt skip=`echo 4528*8192|bc` ibs=1 count=8192;
记录了8192+0 的读入
记录了16+0 的写出
8192字节(8.2 kB,8.0 KiB)已复制,0.0036946 s,2.2 MB/s
## 2. 使用xxd命令查看二进制文件,可以观察到insert的DUMP_STR可以在二进制中看到
[root@localhost DAMENG]# xxd test1.txt
00000000: 0b00 0000 b011 0000 ffff ffff ffff 0000 ................
00000010: b111 0000 1400 0000 b9a2 a3d0 bbcd cf00 ................
00000020: 0000 0000 e500 1c1e 0000 0000 e300 ffff ................
00000030: 5200 5a00 0000 ee1f 0000 f70f 0002 0000 R.Z.............
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 ffff ffff ffff ................
00000060: ffff 0020 0001 0000 0001 0000 0081 3101 ... ..........1.
00000070: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000080: 0000 0020 0002 0000 0002 0000 0081 3202 ... ..........2.
00000090: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
000000a0: 0000 0020 0003 0000 0003 0000 0081 3303 ... ..........3.
000000b0: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
000000c0: 0000 0020 0004 0000 0004 0000 0081 3404 ... ..........4.
000000d0: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
000000e0: 0000 0020 0005 0000 0005 0000 0081 3505 ... ..........5.
000000f0: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000100: 0000 0020 0006 0000 0006 0000 0081 3606 ... ..........6.
00000110: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000120: 0000 0020 0007 0000 0007 0000 0081 3707 ... ..........7.
00000130: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000140: 0000 0020 0008 0000 0008 0000 0081 3808 ... ..........8.
00000150: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000160: 0000 0020 0009 0000 0009 0000 0081 3909 ... ..........9.
00000170: 0000 0000 00ff ffff ff7f ffff e829 1100 .............)..
00000180: 0000 0021 000a 0000 000a 0000 0082 3130 ...!..........10
00000190: 0a00 0000 0000 ffff ffff 7fff ffe8 2911 ..............).
000001a0: 0000 0000 2100 0b00 0000 0b00 0000 8231 ....!..........1
000001b0: 310b 0000 0000 00ff ffff ff7f ffff e829 1..............)
000001c0: 1100 0000 0021 000c 0000 000c 0000 0082 .....!..........
000001d0: 3132 0c00 0000 0000 ffff ffff 7fff ffe8 12..............
000001e0: 2911 0000 0000 2100 0d00 0000 0d00 0000 ).....!.........
000001f0: 8231 330d 0000 0000 00ff ffff ff7f ffff .13.............
00000200: e829 1100 0000 0021 000e 0000 000e 0000 .).....!........
00000210: 0082 3134 0e00 0000 0000 ffff ffff 7fff ..14............
00000220: ffe8 2911 0000 0000 2100 0f00 0000 0f00 ..).....!.......
00000230: 0000 8231 350f 0000 0000 00ff ffff ff7f ...15...........
00000240: ffff e829 1100 0000 0021 0010 0000 0010 ...).....!......
00000250: 0000 0082 3136 1000 0000 0000 ffff ffff ....16..........
00000260: 7fff ffe8 2911 0000 0000 2100 1100 0000 ....).....!.....
00000270: 1100 0000 8231 3711 0000 0000 00ff ffff .....17.........
00000280: ff7f ffff e829 1100 0000 0021 0012 0000 .....).....!....
00000290: 0012 0000 0082 3138 1200 0000 0000 ffff ......18........
000002a0: ffff 7fff ffe8 2911 0000 0000 2100 1300 ......).....!...
000002b0: 0000 1300 0000 8231 3913 0000 0000 00ff .......19.......
000002c0: ffff ff7f ffff e829 1100 0000 0021 0014 .......).....!..
000002d0: 0000 0014 0000 0082 3230 1400 0000 0000 ........20......
000002e0: ffff ffff 7fff ffe8 2911 0000 0000 2100 ........).....!.
附录
SYSOBJECTS表与聚集索引方式比较测试
SYSOBJECTS表的方式,导出10个数据块
dd if=DMUPUSER.DBF of=test1.txt skip=`echo 16*8192|bc` ibs=1 count=81920;
v$segmentinfo表的方式,导出10个数据块
使用脚本生成的命令
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 16*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 97*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 98*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 99*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 100*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 101*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 102*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 103*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 104*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 105*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# dd if=DMUPUSER.DBF of=test2.txt skip=`echo 106*8192|bc` ibs=1 count=8192 conv=notrunc oflag=append;
[root@localhost DAMENG]# xxd test2.txt | grep -v '0000 0000 0000 0000 0000 0000 0000 0000' |head -n 1100| tail -n 100
两个文本比较,查看第1000行对比如下:
- 左侧的是SYSOBJECTS表导出的数据页是无序的,右侧v$segmentinfo的方式是有序且有规律的。
- 当表中数据物理页不连续时,采用SYSOBJECTS表的方式是错误的。
批量导出数据页的脚本
/**
修改三个参数
table_owner 模式名
table_name 表名
print_count 要打印的页数
**/
declare
table_owner varchar(256);
table_name varchar(256);
datafile_name varchar(256);
sql_str varchar(512);
page_size int;
i NUMBER := 0;
print_count int;
PHY_PAGE_NO int;
CURSOR sel_cur;
begin
table_owner := 'DMUPUSER';
table_name := 'PAGETEST';
print_count := 10;
-- 查询出表所在的表空间
sql_str:= 'select tablespace_name from dba_indexes where table_owner =''' || table_owner || ''' AND INDEX_TYPE = ''CLUSTER''' || ' AND INDEX_NAME LIKE ''INDEX%''' || 'AND TABLE_NAME = ''' || table_name||'''';
--print 'sql_str' || sql_str;
execute immediate sql_str into datafile_name;
-- 查询出当前页大小
select page into page_size from dual;
--PRINT 'datafile_name=' || datafile_name;
-- 查询块sql
sql_str:= 'select PHY_PAGE_NO from v$segmentinfo where index_id=( select OBJECT_ID from dba_indexes a,' || 'dba_objects b where TABLE_NAME=''' ||table_name|| ''' and a.INDEX_NAME=B.OBJECT_NAME and b.owner = ''' || table_owner || ''' AND INDEX_TYPE = ''CLUSTER'' AND INDEX_NAME LIKE ''INDEX%'' )';
--PRINT 'sql_str ' || sql_str;
open sel_cur for sql_str;
LOOP
FETCH sel_cur INTO PHY_PAGE_NO;
EXIT WHEN sel_cur%NOTFOUND OR i >print_count ;
i := i + 1;
print 'dd if=' || datafile_name || ' of=test1.txt skip=`echo '||PHY_PAGE_NO||'*'||page_size||'|bc` ibs=1 count='||page_size||' conv=notrunc oflag=append;';
END LOOP;
CLOSE sel_cur;
end;
dd两个命令介绍,使用下方命令参数可将源文件的内容追加到目标文件的末尾,并保留目标文件原有的内容。
- conv=notrunc:表示不截断目标文件。
- oflag=append:表示在目标文件末尾追加数据。
xxd的命令
## 备用命令
## 1. 查看文件时,去除空数据
[root@localhost DAMENG]# xxd test1.txt | grep -v '0000 0000 0000 0000 0000 0000 0000 0000' |head -n 100
## 2. 查看前几行
[root@localhost DAMENG]# xxd test1.txt|head -n 50
## 3. hexdump命令
[root@localhost DAMENG]# hexdump -C test1.txt
## hexdump -C 参数是 hexdump 命令的一个选项,表示以十六进制和 ASCII 的形式显示文件内容,每行显示对应的 ASCII 字符。