级别: 初级
郝庆运 (haoqingy@cn.ibm.com), 软件工程师, IBM
黄耀华 (yaohuah@cn.ibm.com), 软件工程师, IBM
2009 年 6 月 05 日
在本文中我们将利用 db2dart 工具来理解 DB2 数据的存储方式,特别是 DB2 数据在每个页中是如何存储的。
简介
DB2 数据库中的数据表存放在表空间中,每个表空间可以有一个或者多个容器(Container),页(page)是容器中最基本的存储单元,每个页可以存储一定数量的记录。可是每一条记录在页中的存储是什么样子的?有没有什么工具能够看到每个页中存储了哪几条记录?
db2dart 是 DB2 中自带的一个数据库分析利器,可以分析数据库、表空间和表等,验证数据库及其对象的体系结构是否正确,还可以使用它来显示数据库控制文件的内容,以便从其他情况下可能无法访问的表中抽取数据。
在本文中将通过 db2dart 来显示和分析数据库中的数据,从而深入理解 DB2 的数据存储方式,这对于提高 DBA 数据恢复技能也有一定的帮助。
DB2 中数据存储方式概述
DB2 数据库中有两种类型的表空间,它们都可以在单个数据库中使用:
- 系统管理的空间(SMS),操作系统的文件管理器控制其中的存储空间。
- 数据库管理的空间(DMS),数据库管理器控制其中的存储空间。
还可以创建自动存储器表空间,该表空间将使用 SMS 或 DMS 作为基本表空间类型。数据库管理器将根据其中包含的数据类型选择实际类型 SMS 或 DMS(SMS 用于临时表空间,DMS 则用于其他表空间)。
本文中我们只针对 DMS 类型的表空间进行讨论。
表空间是一种存储结构,它包含表、索引、大对象和长型数据。它们允许将数据库和表数据的位置直接指定到容器上。容器可以是目录名、设备名或文件名,单个表空间可跨多个容器,如图 1 所示。
图 1. 数据库中的表空间、容器和表
DB2 数据库管理器会平衡分布在所有容器中的数据负荷,所有容器都将用于存储数据。数据库管理器在使用另一个容器之前写入一个容器的页数称为扩展数据块(extent)大小。数据库管理器并非始终从第一个容器开始存储表数据。在图 2 中,表空间 HUMANERS 有 4 个容器,扩展数据库大小为 2 页,每个页大小是 4K 字节。 DEPARTMENT 和 EMPLOYEE 表都有 7 页,并且都跨越所有四个容器。
图 2. 表空间中的容器和扩展数据库
那每个页中的记录是如何存储的呢,接下来我们利用 db2dart 工具来更为直观的进行理解。
db2dart 使用简介
工欲善其事必先利其器,那我们就先来看看 db2dart(DB2 Database Analysis and Reporting Tool)这个工具。如上文所述,db2dart 更多的是一个诊断工具,可以用来验证数据库以及相关的数据库对象是否正确,是否存在问题,还可以用来显示数据库控制文件的内容,以便在重建数据库时从其他情况下无法访问的表中抽取数据。
如果想查看 db2dart 的相关语法,可以在命令窗口(DB2CLP)中,发出 db2dart 命令(不带任何参数),就可以看到其所有选项及其功能。在本文中我们将要用到的是:
/DD:提取格式化的表数据。
命令格式:db2dart <database name> /DD /TSI … /OI … /PS … /NP ... /V Y
|
在上面的命令中:
- /TSI 用于指定表空间 ID(tablespace ID),
- /OI 用于指定对象 ID(object ID),
- /PS 用于指定开始的数据页,
- /NP 用于指定所要提取的数据页的数量,
- /V 用于设定是否显示详细信息,Y:显示,N:不显示。
这里的表空间 ID 和对象 ID 可以从 CATALOG 表 SYSCAT.TABLES 中获取,下文中将给出具体的 SQL 语句。此外还可以使用 /RPTN 指定用来保存结果的文件名。
注意,db2dart 不能在仍具有活动连接的数据库上运行。如果存在活动连接,db2dart 将输出警告信息,而且有可能输出错误的结果。可以通过“ FORCE APPLICATIONS ALL ”命令断开所有的数据库连接。也正是因为这一离线工作的特性,使得 db2dart 在数据库崩溃之后的恢复中能够发挥巨大的作用。
准备工作
在开始之前,我们先创建一个新的数据库名字叫做 MYDB,如清单 1 所示。当然用已经存在的数据库也可以,但是为了能够简化环境,清楚的、逐步的进行我们接下来的讨论,建议使用一个全新的数据库。
本文中所有操作都可在 LinuxAMD64 平台上的 DB2 V95 及之后的版本进行,若其他数据库版本或者其他平台,输出结果可能稍微有所差别。
清单 1. 创建数据库
创建数据库之后我们检查一下表空间的情况,使用的命令及结果如清单 2 所示。
清单 2. 查看表空间
bash-3.1$ db2 list tablespaces
Tablespaces for Current Database
Tablespace ID = 0
Name = SYSCATSPACE
Type = Database managed space
Contents = All permanent data. Regular table space.
State = 0x0000
Detailed explanation:
Normal
Tablespace ID = 1
Name = TEMPSPACE1
Type = System managed space
Contents = System Temporary data
State = 0x0000
Detailed explanation:
Normal
Tablespace ID = 2
Name = USERSPACE1
Type = Database managed space
Contents = All permanent data. Large table space.
State = 0x0000
Detailed explanation:
Normal
|
这三个表空间是创建数据库时默认创建的,其中表空间 USERSPACE1 用来保存永久性数据,当我们创建新表时 USERSPACE1 将是默认的表空间。我们也可以创建新的表空间并且在创建新表时指定特定的表空间,但在本文中我们将一直使用 USERSPACE1 。
需要特别指出的是,USERSPACE1 的类型是 Database managed space(DMS)即数据库管理空间表空间,与之相对的是 System managed space(SMS,系统管理空间),至于二者的区别在此不进行解释,读者可以从参考资源部分的“ DB2 信息中心”找到相关内容。
接下来我们看一下表空间 USERSPACE1 的一些细节信息,在我们接下来的讨论中将会与用到这些信息。使用的命令及结果如清单 3 所示。
清单 3. 查看表空间 USERSPACE1 详情
bash-3.1$ db2 list tablespaces show detail
Tablespaces for Current Database
// 此处删除若干行
Tablespace ID = 2
Name = USERSPACE1
Type = Database managed space
Contents = All permanent data. Large table space.
State = 0x0000
Detailed explanation:
Normal
Total pages = 256
Useable pages = 224
Used pages = 96
Free pages = 128
High water mark (pages)
= 96
Page size (bytes)
= 4096
Extent size (pages)
= 32
Prefetch size (pages)
= 32
Number of containers
= 1
|
表空间是一个页集(page set),页(page)是 DB2 中表空间、索引空间或虚拟内存中的存储单元,多数情况下在表空间中页可包含表的一行或多行。在清单 3 中我们可以看到,这个表空间一共有 256 个页(Total pages = 256),可以用的是 224 个页(Useable pages = 224),已用了 96 个页,空闲的 128 个页。除此之外的那 32 个页(256 - 224)是被 DB2 用作了某种开销。
每个页的大小是 4096 bytes(Page size (bytes) = 4096),这样我们可以计算出这个表空间的大小是:4096 bytes * 256 = 1 MB,这个表空间虽然比较小,但对于我们接下来的操作和讨论已经足够了。
利用 db2dart 来看空的表
创建一个简单的关系型表 TB1,并获取这个表的表空间 ID 和对象 ID,在下文中我们将多次使用这对 ID 作为 db2dart 的输入。使用的 SQL 以及结果如清单 4 所示。
清单 4. 创建表
CREATE TABLE TB1(s1 SMALLINT, v1 VARCHAR(256))
SELECT SUBSTR(TABNAME,1,30) TABNAME, TABLEID,TBSPACEID
FROM SYSCAT.TABLES WHERE TABNAME='TB1'
TABNAME TABLEID TBSPACEID
----------------------- ------- ---------
TB1 4
2
1 record(s) selected.
|
现在我们已经创建了一个关系型表 TB1,尚未插入任何的数据,我们使用 db2dart 来提取表数据,来看看一个空表中的信息,同时熟悉一下 db2dart 的使用。这里我们用的是 /DD 操作,用 /TSI 2 和 /OI 4 来指定表 TB1,从第 0 页开始提取,一共提取 100 页。为什么我们要提取 100 页而不是其他的数量呢?没有关系,在第一次使用该命令时我们可以选择任意数量,然后根据得到的报告以及之后的需求来调整这个数量,事实上在这里我们完全可以使用一个很小的数字,因为我们面对的是一个空表。使用的命令以及输出如清单 5 所示。
清单 5. 使用 db2dart 提取空表信息
bash-3.1$ db2 connect reset
DB20000I The SQL command completed successfully.
bash-3.1$ db2dart mydb /DD /TSI 2 /OI 4 /PS 0 /NP 100 /V Y /RPTN MYDB.RPT_TB1_empty
____________________________________________________________________
_____ D B 2 D A R T _____
Database Analysis Tool
I B M
DB2 6000
The DB2DART Tool is a utility for the analysis of databases,
tablespaces, and tables. DART's primary function is to
examine databases for their architectural correctness, and to
report any encountered errors.
____________________________________________________________________
______________________________________________________________________
_______ DART _______
D a t a b a s e A n a l y s i s a n d R e p o r t i n g T o o l
IBM DB2 6000
______________________________________________________________________
DART (V9.5) Report:
2009-03-11-07.45.34.651415
Database Name: D
Report name: D.RPT_TB1_empty
Database Subdirectory: /work1/haoqingy/haoqingy/NODE0000/SQL00001
Operational Mode: Database Inspection Only (INSPECT)
______________________________________________________________________
----------------------------------------------------------------------
Connecting to Buffer Pool Services...
Table object report phase start.
Formatted data being dumped to report...
Dump format is verbose.
Dumping...
Dumping...
Table object report phase end.
______________________________________
The requested DB2DART processing has completed successfully!
All operation completed without error;
no problems were detected in the database.
______________________________________
Complete DB2DART report found in: MYDB.RPT_TB1_empty
_______ D A R T P R O C E S S I N G C O M P L E T E _______
|
接下来我们较为详细的讨论 db2dart 得到的报告,存放在下载部分的文件 MYDB.RPT_TB1_empty 中。如果我们没有指定 /RPTN 参数,默认的报告存放在 MYDB.RPT 即数据库名字加上 .RPT 的后缀。报告的内容如清单 6 所示(为便于阅读删除了部分信息以及空行)
清单 6. 使用 db2dart 提取空表信息所生成的报告
______________________________________________________________________
_______ DART
_______
D a t a b a s e A n a l y s i s a n d R e p o r t i n g T o o l
IBM DB2 6000
______________________________________________________________________
DART (V9.5) Report:
2009-03-10-23.43.32.808469
Database Name: MYDB
Report name: MYDB.RPT_TB1_empty
Database Subdirectory: /work1/haoqingy/haoqingy/NODE0000/SQL00001
Operational Mode: Database Inspection Only (INSPECT)
______________________________________________________________________
----------------------------------------------------------------------
Action option: DD
Table-object-ID: 4; Tablespace-ID: 2; First-page: 0; Number-pages: 100; Verbose: y
Connecting to Buffer Pool Services...
Table object report phase start.
Dump format is verbose.
______________________________________
Page 0 of object 4 from table space 2.
BPS Page Header:
Page Data Offset = 48
Page Data Length = 4048
Page LSN = 000000000234041A
Object Page Number = 0
Pool Page Number = 128
Object ID = 4
Object Type = Data Object
Data Page Header:
Slot Count = 4
Total Free Space = 2884
Total Reserve Space = 0
Youngest Reserve Space = n/a
Youngest TID = n/a
Free Space Offset = 2891
Maximum Record Size = 0
Data Records:
Slot 0:
Offset Location = 3996 (xF9C)
Record Length = 32 (x20)
Record Type = Data Object Header Control Record
Page count = 1
Object Creation LSN = 000000000234041A
Object State = x0000
UDI Since Runstats = 0
DFH flag bits = x00000000
Row Change Timestamp = x00000000000000
Slot 1:
Offset Location = 2992 (xBB0)
Record Length = 1004 (x3EC)
Record Type = Free Space Control Record
Free space entries:
0: 2884 (x0B44), 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC)
4: 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC)
8: 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC)
12: … .. 此处删除若干行
492: 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC)
496: 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC), 4028 (x0FBC)
Slot 2:
Offset Location = 2916 (xB64)
Record Length = 76 (x4C)
Record Type = Table Directory Record
TDIR version = 2
MetaIndex Does Not Exist
Max Insert Search = 0
Dictionary RID -- Page 0 Slot 0
Mapping Directory RID -- Page 0 Slot 0
Current table row format version = 0
Flags = x0000000106000000
bit representation = 00000000 00000000 00000000 00000001
00000110 00000000 00000000 00000000
Check pending info:
Constraint status = x00
Constraint RID page = 0
last BID page = x00000000
Slot 3:
Offset Location = 2892 (xB4C)
Record Length = 24 (x18)
Record Type = Table Description Record
Number of Columns = 2
Column 1:
Type is Small Integer
Length = 2
Allows NULLs
Prohibits Default
Fixed offset: 0
Column 2:
Type is Variable Length Character String
Maximum Length = 256
Allows NULLs
Prohibits Default
Fixed offset: 3
Slots Summary: Total=4, In-use=4, Deleted=0.
Table object report phase end.
______________________________________
The requested DB2DART processing has completed successfully!
All operation completed without error;
no problems were detected in the database.
______________________________________
Complete DB2DART report found in: MYDB.RPT_TB1_empty
_______ D A R T P R O C E S S I N G C O M P L E T E _______
|
在这个报告中,我们只看到了这个表的 page 0,这是因为其他的页当前都是空的,所以 db2dart 没有进行提取,而且如果我们 /NP 的值改为 1 也可以得到相同的结果。在 page 0 首先是一个记录相关信息的 page header,接着又分成了 4 个 slot,slot 是 page 中通过偏移(offset)划分出来的更小的不规则的存储空间。
- slot 0 中的记录是 Data Object Header Control Record,用来记录一些数据对象(表)的控制信息;
- slot 1 中的记录是 Free Space Control Record 即 FSCR,用来记录接下来 500 个页中每个页的可用空间近似值;
- slot 2 中的记录是 Table Directory Record,用来记录这个表(TB1)的目录信息;
- slot 3 中的记录是 Table Description Record 即对这个表的描述,这里可以看到这个表有多少列(Number of Columns = 2),第一列的类型是 Small Integer,长度是 2,允许为空,没有默认值,第二列的类型是 Variable Length Character String,长度为 256,允许为空,没有默认值。这与我们之前创建表 TB1 的语句(CREATE TABLE TB1(s1 SMALLINT, v1 VARCHAR(256)))是完全一致的。
需要指出的是,并非每一个 page 的前 4 个 slot 都是以上内容,我们当前看的是这个表对象的第一个页即 page 0,所以在这个 page 中记录这个表的相关信息,此外 FSCR 会在第 501 个 page 中再次出现用来记录从 501 页到 1000 页的每个页的可用空间近似值。
利用 db2dart 来看 DB2 数据
插入一条记录再利用 db2dart 来看
我们已经讨论过了一个空的表中结构和信息,接下来看用户插入的数据是如何存储的,为了便于更加清晰的看到用户数据,我们先只插入一条记录然后用 db2dart /DD 来看。使用的命令如清单 7 所示。
清单 7. 插入一条记录然后用 db2dart 来提取
db2 “ insert into tb1 values(123,'abc') ”
db2 force applications all
db2dart MYDB /DD /TSI 2 /OI 4 /PS 0 /NP 100 /V Y /RPTN MYDB.RPT_TB1_1Row
|
在下载部分的报告文件 MYDB.RPT_TB1_1Row 中可以看到,其内容与清单 6 相似,在此不需要展示其全部内容,差别在于 BPS Page Header 中的一些数值发生变化,如 slot Count,Total Free Space 等,以及出现了一个新的 slot 4,如清单 8 所示。
清单 8. 用 db2dart 看到的新插入的行
Slot 4:
Offset Location = 2873 (xB39)
Record Length = 19 (x13)
Record Type = Table Data Record (FIXEDVAR) (PUNC)
Record Flags = 0
Fixed part length value = 8
Column 1:
Fixed offset: 0
Type is Small Integer
Value = 123
Column 2:
Fixed offset: 3
Type is Variable Length Character String
Length = 3 Offset = 8
616263
abc
|
在 slot 4 中存放的记录是 Table Data Record,也就是真正的用户数据,可以看到第一列(column 1)的值是 123(value=123),第二列的值是 616263 即 abc,这正是我们之前所插入的记录。那插入的每条记录分别对应一个 slot 呢还是多条记录可以存放在同一个 slot 中呢,我们接下来继续讨论。
插入多条记录再利用 db2dart 来看
向表 TB1 中插入多条记录,然后使用 db2dart /DD 来提取,并将报告保存在下载部分的 MYDB.RPT_TB1_10Row 文件中,使用的 SQL 语句以及命令如清单 9 所示。
清单 9. 插入多条记录
db2 connect to mydb
db2 "insert into tb1 values(1231,'abc1')"
db2 "insert into tb1 values(1232,'abc2')"
db2 "insert into tb1 values(1233,'abc3')"
db2 "insert into tb1 values(1234,'abc4')"
db2 "insert into tb1 values(1235,'abc5')"
db2 "insert into tb1 values(1236,'abc6')"
db2 "insert into tb1 values(1237,'abc7')"
db2 "insert into tb1 values(1238,'abc8')"
db2 "insert into tb1 values(1239,'abc9')"
db2 connect reset
db2 force applications all
db2dart MYDB /DD /TSI 2 /OI 4 /PS 0 /NP 100 /V Y /RPTN MYDB.RPT_TB1_10Row
|
在文件 MYDB.RPT_TB1_10Row 中可以看到,新插入的每一条记录分别对应着一个 slot,即从 slot 5 到 slot 13 。而且我们注意到所有的这些 slot 都仍然在 page 0 中,那么一个页究竟能分成多少个 slot(即存放多少条记录)呢?我们下面对这个问题进行讨论。
我们从清单 6 中提取一些信息出来,如清单 10 所示。
清单 10. slot 的偏移位置和记录长度
Page 0 of object 4 from table space 2.
BPS Page Header:
Page Data Offset = 48
Page Data Length = 4048
Slot 0:
Offset Location = 3996 (xF9C)
Record Length = 32 (x20)
Slot 1:
Offset Location = 2992 (xBB0)
Record Length = 1004 (x3EC)
Slot 2:
Offset Location = 2916 (xB64)
Record Length = 76 (x4C)
Slot 3:
Offset Location = 2892 (xB4C)
Record Length = 24 (x18)
|
我们保留了每一个 slot 的两个数字,并且发现它们之间的关系:
3996 – 2992 = 1004 ;
2992 – 2916 = 76 ;
2916 – 2892 = 24 ;
|
可以看出,存储这些 slot 是从 page 的尾部开始,后来的 slot 接着前一个 slot 的位置进行存放,并用上一个 slot 的偏移位置减去自身的长度(record length)作为自己的偏移位置,如图 3 所示。
图 3. page 中的 slot
每一个 slot 的长度由这条记录的数据的长度和保存 slot 信息的固定长度组成,由于数据的长度是可变的所以每个 slot 的长度是不固定的,例如 slot 4 的长度是 19 字节,而 slot 5 到 slot 13 的长度是 20 字节。当 page 中没有足够的空闲空间分配给下一个 slot 时,DB2 将使用下一个 page 。我们接下来对此进行验证。
page 0 什么时候用完?
用清单 11 中的命令向表 TB1 中插入 10 条记录,每条记录长度较长,然后用 db2dart /DD 提取,将结果保存在下载部分的 MYDB.RPT_TB1_10LongRow 文件中。从该文件中我们可以看到,所有的记录仍然在 page 0 当中。
清单 11. slot 的偏移位置和记录长度
db2 connect to mydb
db2 "insert into tb1 values(1230,'abc0tttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttt')"
db2 "insert into tb1 values(1231,'abc1tttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttt')"
db2 "insert into tb1 values(1232,'abc2ttttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
ttttttttttt')"
// 反复执行上一条语句 7 次,共插入 10 条记录
db2 force applications all
db2dart mydb /DD /TSI 2 /OI 4 /PS 0 /NP 100 /V Y /RPTN MYDB.RPT_TB1_10LongRow
|
我们再一次利用清单 11 中的命令向表中再插入 10 条记录,然后将 db2dart /DD 的结果保存到下载部分 MYDB.RPT_TB1_20LongRow 文件中。在这个结果中我们可以看到,这 10 条记录全部被存放在 page 1 上,也就是说 page 0 已经没有足够的空间存放其中的任何一条记录。在 page 1 中的 slot 0 到 slot 4 存放的是我们刚才插入的前四条记录,这与 page 0 中存放的特殊用途的记录(见清单 6)不同。
page 0 真的满了吗?
需要注意的是,并不能说只有当前页完全用完才会启用新的页,例如当前空闲空间还有 100 字节,而下一个 slot 的长度有 101 字节,那么这个 slot 将被放到下一个 page 中,在此之后用户又插入一个长度只有 10 字节的记录,那么存放这条记录的 slot 仍然可以在前一个 page 中分配。现在我们来验证 page 0 中是否还能够存放新的记录。从 page 0 的 page header 中可以看到还有 135 字节的空闲空间(Total Free Space = 135),此时向表中插入一条较短的记录,然后将 db2dart /DD 的结果保存在下载部分的 MYDB.RPT_TB1_1MoreRow 文件中,使用的命令如清单 12 所示。在这个结果中我们可以看到,新的记录被存放到 page 0 的 slot 24 中,这就说明 page 0 中还可以继续存放新的记录。
清单 12. 插入一条较短的记录
db2 connect to mydb
db2 "insert into tb1 values(9999,'aaaa')"
db2 force applications all
db2dart mydb /DD /TSI 2 /OI 4 /PS 0 /NP 100 /V Y /RPTN MYDB.RPT_TB1_1MoreRow
|
我们可以继续向表中插入这种较短的记录,或者空记录(NULL)直到 page 0 再也无法存放任何记录。那是不是说一定要等到当前页没有足够的空间时才会写入下一页呢?不是的。我们可以在创建表的时候指定 PCTFREE 选项,用来设定每个页至少保留的空闲空间比例,当空闲空间达到这个比例时新的记录将不再写入这个页。每个页的第一条记录不受此限制。这个值可以设定在 0-99 任意一个数字,-1 表示默认值,默认的值为 0 。我们把这些留给读者去验证。
总结
在文中我们利用 db2dart 工具真切的看到了数据在 DB2 数据库中是如何存储的,存储空间在每个页上是如何分配的。本文仅限于 insert 了简单数据的情况,算是抛砖引玉,读者可以继续利用 db2dart 工具探讨 update、delete 等操作之后数据存储发生了什么变化,也可以探讨 pureXML 中的 XML 文档的存储。