引言
在 Informix 中,可以通过索引来快速定位“需要使用的数据行”。那么一般的 Informix 索引是通过什么结构来实现“快速定位数据行”的目的呢?本文将通过 oncheck 命令来展示实际的 Informix 索引结构,为您揭晓这个问题的答案。
Informix 索引简介
在 Informix 中,一个数据表里可能有很多数据行(row)。例如某连锁超市的“客户基本信息”数据表里有 7 百万个数据行(即 7 百万个客户的基本信息)。Informix 收到超市工作人员输入的“某个客户的客户号”后,需要快速的向超市工作人员返回“这个客户的姓名、邮箱、住址等信息”。如果 Informix 收到“某个客户的客户号”后,把“客户基本信息”数据表的 7 百万个数据行从前往后一行一行顺序扫描,那么 Informix 将无法快速返回结果。如何才能达到“快速返回结果”的目的呢?答案是使用 Informix 索引。Informix 索引就像是中文字典里的拼音目录。拼音目录里记录了每个拼音和对应的页码,Informix 索引里记录了每个特征值(例如每个客户的客户号)和对应的数据行地址。通过拼音目录可以快速的得到某个拼音对应的页码,通过 Informix 索引可以快速的得到某个特征值(例如某个客户的客户号)对应的数据行地址。
在 Informix 中一般的索引是通过 B+ 树这种数据结构来实现的。B+ 树是一种树结构,它的每个节点可以有多个分叉。B+ 树可以是 1 层或多层。在 B+ 树中,所有的叶节点都在同一层,即不会出现类似这样的情况:在一棵 B+ 树中,有的叶节点在第 2 层,有的叶节点在第 3 层。关于 B+ 树的详细叙述请参看 Wikipedia 上关于 B+ 树的内容。B+ 树的结构如图 1 所示。
图 1. 图 1. B+ 树的结构
图 1 中的 B+ 树一共有 3 层:第 1 层是根节点,第 2 层是分支节点,第 3 层是叶节点。节点里有多个节点项 (entry),每个节点项由键 (key) 和值 (value) 组成。节点项的键一般为整数、字符串或“ ‘元素是整数或字符串’ 的集合”。在图 1 的节点中,节点项的键为整数。节点项的值 (value) 一般为指针。在非叶节点中,节点项的值指向子节点。在叶节点中,节点项的值为空指针或指向具体数据的指针。在图 1 的叶节点中,节点项的值指向数据行。一般情况下,每个节点还有两个指针,分别指向“前一个兄弟节点”和“后一个兄弟节点”。
Informix 索引对应的 B+ 树一般是 2 层、3 层或 4 层。
在 Informix 索引对应的 B+ 树中,1 个节点是 1 个页 (page)。
oncheck 命令的简介
在 Informix 中,oncheck 命令可以检查和打印数据对象。例如:
- oncheck -cr 可以检查数据库保留页的正确性
- oncheck -cD <databaseName>:<tableName> 可以检查数据的正确性
- oncheck -cI <databaseName>:<tableName>#<indexName> 可以检查索引的正确性
- oncheck -pe <dbsName> 可以打印某个 dbspace 中所有 extent 的分布情况
- oncheck -pP <chunkNum> <startPageNum> <numOfPages> 可以打印指定的数据页
- oncheck -pT <databaseName>:<tableName> 可以打印“某个数据表以及其上的索引”的磁盘使用情况
关于 oncheck 命令的详细叙述请参看 Informix Information Center 中关于 oncheck 的内容。
准备工作
本文通过一个例子来讲述“如何通过 oncheck 命令来展示 Informix 索引结构”。通过以下步骤来创建这个例子所需的数据对象:
(1)首先通过如下的 SQL 语句在名为 dbs1 的 dbspace 中创建数据库 db1:
1 |
|
(2)然后使用如下的 SQL 语句在 db1 中创建数据表、加载数据、创建索引:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
上面的 SQL 语句创建了名为 employee 的数据表,该数据表有 6 个数据列:employeenum 是员工号,deptnum 是部门号,profnum 是职业种类号,band 是职业等级,name 是员工姓名,description 是对员工的描述。上面的 SQL 语句使用存储过程 employeeProc 往数据表 employee 中插入了 60000 行实验数据。这 60000 行实验数据如表 1 所示。
表 1. 表1. 数据表 employee 中的数据的示例
employeenum | deptnum | profnum | band | name | description |
---|---|---|---|---|---|
1 | 1 | 1 | 1 | name1 | Description of employee 1: she/he is an excellent employee. |
2 | 1 | 1 | 2 | name2 | Description of employee 2: she/he is an excellent employee. |
3 | 1 | 1 | 3 | name3 | Description of employee 3: she/he is an excellent employee. |
4 | 1 | 1 | 4 | name4 | Description of employee 4: she/he is an excellent employee. |
5 | 2 | 1 | 5 | name5 | Description of employee 5: she/he is an excellent employee. |
6 | 2 | 1 | 6 | name6 | Description of employee 6: she/he is an excellent employee. |
... | ... | ... | ... | ... ... | ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... |
60000 | 12001 | 1200 | 50 | name60000 | Description of employee 60000: she/he is an excellent employee. |
(若要查看全部 60000 行实验数据,请参看文件 employee.txt。文件 employee.txt 被包含在附件“Informix 索引结构 .zip”中。)
在数据表 employee 上有 3 个索引:idx1、idx2、idx3。idx1 是单列唯一索引(single-column unique index)。idx2 是单列非唯一索引(single-column non-unique index)。idx3 是多列唯一索引(multi-column unique index)。“单列”意味着索引是建在 1 个数据列上。“多列”意味着索引是建在多个数据列上。“唯一”是指任意两个数据行的索引键不能相同,例如对于唯一索引 idx1,任意两个数据行的 employeenum 不能相同。“非唯一”是指两个数据行的索引键可以相同。
单列唯一索引的结构
我们先来关注单列唯一索引 idx1。如何查看 idx1 的内部结构呢?答案是使用 oncheck 命令。我们使用 oncheck -pT 来查看 idx1 的层次,使用 oncheck -pP 来查看 idx1 的所有节点的内容,具体操作如下(这些 oncheck 命令的语法请参看前文的“oncheck 命令的简介”这一章):
- 使用如下命令将“数据表 employee 以及其上的索引”的磁盘使用情况输出到文件 pT.out 中:
1
oncheck -pT db1:employee > pT.out
(文件 pT.out 被包含在附件“Informix 索引结构 .zip”中)
- 使用如下命令将 dbs1(dbs1 是“employee、idx1、idx2、idx3”所在的 dbspace)中所有 extent 的信息输出到文件 pe.out 中:
1
oncheck -pe dbs1 > pe.out
(文件 pe.out 被包含在附件“Informix 索引结构 .zip”中)
- 在文件 pe.out 中,可以看到如下信息:
1
2
3
4
5
6
7
8
9
10
11
... ...
... ...
Chunk Pathname Pagesize(k) Size(p) Used(p) Free(p)
10 /opt/ts/dbs/dbs1 2 500000 6015 493985
Description Offset(p) Size(p)
... ...
... ...
db1:'informix'.idx1 4607 512
... ...
... ...
这说明索引 idx1 由一个 extent 组成 ( 一个 extent 是一组物理上连续的页 ),idx1 所在的 chunk 的 chunk 号是 10,idx1 的起始页在 chunk 内的页地址为 4607,idx1 的总页数为 512。(chunk 号和 chunk 内的页地址组成物理页地址,所以 idx1 的起始页的物理页地址是 10:4607。)于是我们使用如下命令将 idx1 的内容输出到文件 idx1.out 中:
1
oncheck -pP 10 4607 512 > idx1.out
(文件 idx1.out 被包含在附件“Informix 索引结构 .zip”中)
idx1 的层次
从 pT.out 文件中,我们可以看到 idx1 的层次结构如下:
1 2 3 4 5 6 7 8 |
|
idx1 共有 3 层:第 1 层有 1 个节点,该节点是 idx1 的根节点,该节点有 3 个索引项;第 2 层有 3 个节点,这些节点是 idx1 的分支节点,平均每个分支节点有 144 个索引项;第 3 层有 432 个节点,这些节点是 idx1 的叶节点,平均每个叶节点有 138 个索引项。
idx1 的根节点
在文件 idx1.out 中一共有 512 个页。其中哪一页是 idx1 的根节点呢?
在索引中,逻辑页地址为 0 的页(即起始页)是空白页,逻辑页地址为 1 的页是根节点。(一个页的逻辑页地址表示:从起始页(即第 0 页)算起,该页在逻辑上是第几页。)
如果 idx1 由一个 extent 组成 ( 一个 extent 是一组物理上连续的页 ),那么“页地址转换公式”如下:
1 2 |
|
由于 idx1 的起始页的物理页地址为 10:4607(请参看本章“单列唯一索引的结构”的开头部分),idx1 的根节点的逻辑页地址为 1,所以 idx1 的根节点的物理页地址为 10:4608。
在文件 idx1.out 中,可以看到物理页地址为 10:4608 的页(即 idx1 的根节点)的内容如图 2 所示。
图 2. 图 2. idx1 的根节点的内容
在图 2 中,1 个 slot 是 1 个索引项。idx1 的根节点有 3 个 slot,因此 idx1 的根节点有 3 个索引项。第 1 个索引项的键 (key) 为 0x51fd,值 (value) 为 0x3,这说明“第 1 个索引项指向的索引页”的逻辑页地址为 0x3,也就是说“根节点的第 1 个子节点”的逻辑页地址为 0x3。第 2 个索引项的键 (key) 为 0xa3fa,值 (value) 为 0x9c,这说明“第 2 个索引项指向的索引页”的逻辑页地址为 0x9c,也就是说“根节点的第 2 个子节点”的逻辑页地址为 0x9c。第 3 个索引项由于是根节点的最后一个索引项,所以没有键 (key),只有值 (value)。第 3 个索引项的值 (value) 为 0x134,这说明“第 3 个索引项指向的索引页”的逻辑页地址为 0x134,也就是说“根节点的第 3 个子节点”的逻辑页地址为 0x134。
在图 2 的右上部分,可以看到 next 和 prev。next 表示后一个兄弟节点的逻辑页地址,next 的值为 0 表示没有后一个兄弟节点;prev 表示前一个兄弟节点的逻辑页地址,prev 的值为 0 表示没有前一个兄弟节点。由于根节点没有兄弟节点,所以在根节点中 next 的值和 prev 的值都为 0。
idx1 的分支节点
在 idx1 的第 2 层有 3 个分支节点。这 3 个分支节点是 idx1 的根节点的子节点。
下面我们以 idx1 的第 1 个分支节点(即根节点的第 1 个子节点,为叙述方便,下文简称为分支节点 B)为例来讲述 idx1 的分支节点。
在上一节中,我们已经知道分支节点 B(即根节点的第 1 个子节点)的逻辑页地址为 0x3,根据上一节提到的“页地址转换公式”,可以得知分支节点 B 的物理页地址为 10:4610。
在文件 idx1.out 中,可以看到物理页地址为 10:4610 的页(即分支节点 B)的内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
从上面的分支节点 B 的内容中,可以看到分支节点 B 一共有 151 个索引项(即 151 个 slot)。每个索引项都有键 (key) 和值 (value),索引项的值指向一个叶节点。例如:第 1 个索引项的键 (key) 为 0x8b,值 (value) 为 0x2,这说明“第 1 个索引项指向的索引页”的逻辑页地址为 0x2,也就是说“分支节点 B 的第 1 个子节点”的逻辑页地址为 0x2;第 151 个索引项的键 (key) 为 0x51fd,值 (value) 为 0x99,这说明“第 151 个索引项指向的索引页”的逻辑页地址为 0x99,也就是说“分支节点 B 的第 151 个子节点”的逻辑页地址为 0x99。
从上面的分支节点 B 的内容中,还可以看到:next 的值为 9c,prev 的值为 0。这表明分支节点 B 的下一个兄弟节点的逻辑页地址为 0x9c,分支节点 B 没有上一个兄弟节点。
“idx1 的其它分支节点”与分支节点 B 的结构相同。
idx1 的叶节点
在 idx1 的第 3 层有 432 个叶节点。这 432 个叶节点是 idx1 的分支节点的子节点。
下面我们以 idx1 的第 1 个叶节点(即分支节点 B 的第 1 个子节点,为叙述方便,下文简称为叶节点 L)为例来讲述 idx1 的叶节点。
在上一节中,我们已经知道叶节点 L(即分支节点 B 的第 1 个子节点)的逻辑页地址为 0x2,根据“idx1 的根节点”这一节提到的“页地址转换公式”,可以得知叶节点 L 的物理页地址为 10:4609。
在文件 idx1.out 中,可以看到物理页地址为 10:4609 的页(即叶节点 L)的内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
从上面的叶节点 L 的内容中,可以看到叶节点 L 一共有 139 个索引项(即 139 个 slot)。每个索引项都有键 (key) 和值 (value),索引项的值指向一个数据行。例如:第 1 个索引项的键 (key) 为 0x1,值 (value) 为 0x101,这说明“第 1 个索引项指向的数据行”的 ROWID(数据行 ID)为 0x101;第 139 个索引项的键 (key) 为 0x8b,值 (value) 为 0x713,这说明“第 139 个索引项指向的数据行”的 ROWID(数据行 ID)为 0x713。
从上面的叶节点 L 的内容中,还可以看到:next 的值为 4,prev 的值为 0。这表明叶节点 L 的下一个兄弟节点的逻辑页地址为 0x4,叶节点 L 没有上一个兄弟节点。
“idx1 的其它叶节点”与叶节点 L 的结构相同。
idx1 结构的示意图
根据 idx1 的各节点的内容,可以得出 idx1 的结构如图 3 所示。
图 3. 图 3. 单列唯一索引 idx1 的结构
单列非唯一索引的结构
单列非唯一索引和单列唯一索引在内部结构上的区别主要是叶节点:在单列非唯一索引的叶节点中,每个索引项包含 1 个键 (key) 和“1 或多个值 (value)”;在单列唯一索引的叶节点中,每个索引项包含 1 个键 (key) 和“1 个值 (value)”。
下面以“单列非唯一索引 idx2 的第 1 个叶节点”为例来说明单列非唯一索引的叶节点。
idx2 的第 1 个叶节点的内容如下(获取“idx2 的第 1 个叶节点的内容”的过程与获取“idx1 的第 1 个叶节点的内容”的过程相似):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
从 idx2 的第 1 个叶节点的内容中,可以看到该叶节点一共有 55 个索引项。每个索引项都有 1 个键 (key) 和多个值 (value)。例如:第 1 个索引项的键 (key) 为 0x1,多个值 (value) 分别为 0x101、0x102、0x103、0x104,这说明“第 1 个索引项”指向 4 个数据行,这 4 个数据行的 ROWID(数据行 ID)分别为 0x101、0x102、0x103、0x104;第 55 个索引项的键 (key) 为 0x37,多个值 (value) 分别为 0xe0a、0xe0b、0xe0c、0xe0d、0xe0e,这说明“第 55 个索引项”指向 5 个数据行,这 5 个数据行的 ROWID(数据行 ID)分别为 0xe0a、0xe0b、0xe0c、0xe0d、0xe0e。
idx2 的其它节点的内容请参看文件 idx2.out(文件 idx2.out 被包含在附件“Informix 索引结构 .zip”中)。
根据 idx2 的各节点的内容,可以得出 idx2 的结构如图 4 所示。
图 4. 图 4. 单列非唯一索引 idx2 的结构
多列唯一索引的结构
多列唯一索引和单列唯一索引在内部结构上的区别主要是:在多列唯一索引的节点中,索引项的键 (key) 由多个数据列构成;在单列唯一索引的节点中,索引项的键 (key) 由 1 个数据列构成。
下面以“多列唯一索引 idx3 的第 1 个叶节点”为例来说明多列唯一索引的节点。
idx3 的第 1 个叶节点的内容如下(获取“idx3 的第 1 个叶节点的内容”的过程与获取“idx1 的第 1 个叶节点的内容”的过程相似):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
从 idx3 的第 1 个叶节点的内容中,可以看到该叶节点一共有 106 个索引项。每个索引项都有 1 个键 (key) 和 1 个值 (value),索引项的键由两个数据列构成。例如:第 1 个索引项的键 (key) 为(0x1,0x1),值 (value) 为 0x101;第 106 个索引项的键 (key) 为(0x3,0x6),值 (value) 为 0x606。
idx3 的其它节点的内容请参看文件 idx3.out(文件 idx3.out 被包含在附件“Informix 索引结构 .zip”中)。
根据 idx3 的各节点的内容,可以得出 idx3 的结构如图 5 所示。
图 5. 图 5. 多列唯一索引 idx3 的结构
3 种 Informix 索引在内部结构上的特点
从前面的叙述中,可以知道 3 种 Informix 索引在内部结构上的特点如表 2 所示。
表 2. 表 2. 3 种 Informix 索引在内部结构上的特点
索引种类 | 特点 |
---|---|
单列唯一索引 | 在节点中,索引项的键 (key) 由 1 个数据列构成 ; 在节点中,每个索引项有 1 个值 (value)。 |
单列非唯一索引 | 在节点中,索引项的键 (key) 由 1 个数据列构成; 在非叶节点中,每个索引项有 1 个值 (value); 在叶节点中,每个索引项有 1 个或多个值 (value)。 |
多列唯一索引 | 在节点中,索引项的键 (key) 由多个数据列构成; 在节点中,每个索引项有 1 个值 (value)。 |
一些说明
下面是对本文的一些说明:
- 在本文叙述的索引中,索引项都是不跨页的。索引项跨页(即索引项 E 的一部分在页 P1,索引项 E 的另一部分在页 P2 上)的情况较少,也较复杂,不在本文的叙述范围内。
- 多列非唯一索引兼具“多列”和“非唯一”的特点:在节点中,索引项的键 (key) 由多个数据列构成;在非叶节点中,每个索引项有 1 个值 (value); 在叶节点中,每个索引项有 1 个或多个值 (value)。由于篇幅限制,本文不对“多列非唯一索引”进行详细叙述。
- 1 个索引在物理存储上可以由 1 个或多个 extent 组成。extent 的个数不影响索引的逻辑结构。为使叙述更加简明,本文叙述的每个示例索引在物理存储上都由 1 个 extent 组成。
- 在一般的索引中,1 个索引只有 1 个根节点。在 Informix 11.70 中新推出的 Forest-Of-Trees 类型的索引中,1 个索引可以有多个根节点。
- 在一般的索引中,Informix 使用 B+ 树作为索引的内部结构。在一些特殊的索引中,Informix 可能使用其它数据结构作为索引的内部结构。例如在处理空间数据 (spatial data) 时,Informix 使用了 R-Tree 作为索引的内部结构。这些特殊的索引结构不在本文的叙述范围内。
结束语
本文叙述了 3 种 Informix 索引的内部结构,使读者对 Informix 索引的层次、根节点、分支节点、叶节点等有较深的理解。
读者理解 Informix 索引的内部结构后,可以更好的设计 Informix 索引,较大的提升 Informix 数据库和 Informix 应用程序的性能。
转自https://www.ibm.com/developerworks/cn/data/library/techarticle/dm-1208xiey/