简介
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. 创建数据库
db2 CREATE DB mydb
创建数据库之后我们检查一下表空间的情况,使用的命令及结果如清单 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 文档的存储。
--评论
Hi wangzhonnew, 让你看出来了,你说的是对的,呵呵,终于有人来指正这个问题了。
文中的图3确实是说的比较模糊,但这也是事出有因。这是因为这个图是基于db2dart的结果来画的,用已有的数字无法完整体现出真实的page结构,所以这个图更多的是一个示意图(尽管不准确),帮助读者大致理解page中的数据是怎么放置的,而对于page header,slot count,slot list等不是本文讨论的重点。
还有一个很重要的信息没有提到,就是每个slot的offset的起始位置,并不是page的开头(0x00),这也是db2dart的结果中看不来的。
由 haoqingy@cn.ibm.com 于 2010年11月05日发布
图 3. page 中的 slot
好像有问题,page head应该从0x00开始的48字节,然后是slot信息和slot列表\u2026\u2026
slot从前向后增长,数据从后向前\u2026\u2026
从图3看起来,好像说的是page head在最后
由 taoewang 于 2010年10月24日发布
我感觉和涛哥说的不太一样,slot应该是从后往前,数据从前往后吧?varchar的是使用4字节的占位符指定到末尾的offset偏移地址啦存放。
Slot 0:
Offset Location = 8092 (x1F9C)
Record Length = 32 (x20)
Slot 1:
Offset Location = 7088 (x1BB0)
Record Length = 1004 (x3EC)
Slot 2:
Offset Location = 7012 (x1B64)
Record Length = 76 (x4C)
等等,都是page最后开始往前偏移的。但是slot里面的column是按顺序排列,只不过varchar的利用4byte占位符指向后面的Fixed part 。
由 K7VT_wanglei_lei 于 2013年03月08日发布
报告滥用