本章节将详细分析各种常见的导致运行性能统计数值增加的操作及其内部行为的深入探索。通过这样的分析,使得我们在进行语句调优时,能够通过运行性能统计数据迅速的定位到语句的瓶颈 所在、并且着重降低通过优化手段可以避免或减少的操作和内部行为。
7.2.1 逻辑读分析
我们前面提到,逻辑读包括了当前模式读和一致性读。我们将分别分析导致这两种逻辑的原因。
一致性读分析
一致性读过程中会读取到的数据块包括两类:存储对象(表、索引、分区等)的数据块(Data
Block)和回滚数据块(Undo Block)。其中,回滚数据块只有在需要进行一致性回滚的时候才会被读取。此外,由于自从 9i 以后,用户表空间的默认管理方式(也是 Oracle 推荐的方式)是自动端空间管理方式(Auto Segment Space Management,ASSM),对象存储的基本管理信息、数据块的使用情况等都存储在对象存储段头(Segment Header)。
我们下面首先介绍各种访问路径(Access Path)逻辑读的来源。并且,重点以全表扫描为例, 由最简单地环境开始,逐渐引入各种影响因素(如一致性回滚、块清除等),深入分析它们对逻辑 读的影响。
索引扫描
索引扫描是一种单数据块读的操作。它会按照数据在索引上的逻辑顺序逐个读取相应的数据块。
索引结构
要深入了解进行索引扫描时产生的逻辑读次数,我们需要先了解 Oralce 的索引结构。在 Oracle
中,索引的基本数据结构都是 B*Tree,它有以下一些特点: 它由枝节点与叶子节点构成;
其中还有一个特殊的节点————根节点,它是索引树的起始节点。当整个树仅有一个节点 时,唯一的叶子节点也就是根节点;当存在多个节点时,最上层的一个枝节点就是根节点;
每个枝节点指向多个下一层的枝节点或者叶子节点;除根节点外,所有节点都只有一个父节
点;
BTree 是高度平衡的树,所有的叶子节点都处在最低一层上,换句话说,所有叶子节点到根
节点的距离(树的枝节点层数)都是相同的;
叶子节点本身也是一个双向链表,分别有指针指向其左右兄弟节点,这一点是 BTree 与其他树状数据结构相区别的重要标志。
B*Tree 的数据结构示意图如下:

在枝节点中,每条记录对应了一个下一层节点,包含了该节点中起始键值和节点的物理地址; 叶子节点中的索引记录则包含了完整的索引键值(所有索引字段的值,NULL 值除外)和表记录的
ROWID。在 Oracle,根据叶子节点的索引记录的组织方式不同,又可以分为普通索引和位图索引
(Bitmap Index)。
我们可以通过导出索引树来观察索引结构。


其中,
• branch/leaf 表示节点类型;后面分别是节点的 16 进制和 10 进制的数据块地址;
• 括号中第一个数字是当前节点在其父节点中的编号(-1 表示最左边的节点,它在父节点中没有相应的记录,而是在数据块头部中标识。导出数据块的话,可以从 kdxbrlmc———— index branch left most child,中找到;0 为第一条记录,1 为第二条记录,以此类推)
• nrow 表示节点中的记录数(包括了打上了删除标志的记录);
• level 为当前枝节点层数;
• rrow 为叶子节点中的有效(未被删除)的索引记录数;
o 普通索引
对于普通索引,表的每一条非空记录(空记录是指所有索引字段值都为空的记录)在索引的叶子节点中都存在并仅存在一条索引记录;而索引记录中,则存储了对应表记录的索引字段值以及表
记录的 ROWID。
通过将叶子节点数据块导出,我们能更清楚的了解其内容和结构:

其中,kdxlenxt 指向了下一叶子节点、kdxleprv 指向了上一叶子节点。
在每条索引记录中,前面的字段为索引字段值,最后一个字段为表记录的 ROWID 信息。可以按照以下规则转换为实际 ROWID:
• 将数值转换为 2 进制;
• 第 1~10 位为数据文件编号;
• 第 11~32 位为数据块编号;
• 第 33~48 位为数据记录序号;
得到上述数据后,我们可以通过函数 DBMS_ROWID.ROWID_CREATE 转换出实际的 ROWID 来, 以第二条记录(01 80 04 e7 00 08)为例:


提示:对于索引键值相同的多条表记录,则按照其对应的表记录的物理顺序(即 ROWID 顺序)存
储。
o 位图索引
对于位图索引,一段物理位置连续的表记录在索引的叶子节点中由一条索引记录标识。在索引 记录中,除了索引字段值外,还存储了:
• 所映射的连续记录的第一条表记录的 ROWID;
• 所映射的连续记录的最后一条表记录的 ROWID;
• 位图映射表,其每一个二进制位(bit)按顺序对应了联系记录中的一条记录,1 表示该记录中的索引字段值等于当前索引记录的索引字段值,0 则表示不等于;
我们导出一个位图索引的叶子节点块来解释 Oracle 如何由上述三个数据映射到表记录的

我们的索引字段是 T_OBJECTS(STATUS),当前记录中,索引值为’INVALID’:

第 2、3 两个字段分别存储了起始、结束 ROWID,我们可以按照前面方法转换为实际 ROWID:


最后一个字段就是位图映射表了。Oracle 采用了一定的算法对其进行了压缩。映射表中可以有多个分组,每个分组的第一个字节(控制字节)代表了该分组的类型:控制字节转换为 2 进制后,
前 5 位为类型位,表示该组字节的的类型;后 3 位为描述位,根据类型不同会有不同含义。解压缩后的二进制映射位与相应的表记录按倒序映射。
短间隙位组
当类型位的值在 0~23 之间,即 00000~10111 时,该组字节为短间隙位字节,被压缩的二进制位中仅有 1 位为 1,其余为 0。此时,类型位的值还有另外一层含义:有多少间隙字节被压缩,0 表示有 1 个字节、1 表示有 2 个字节、以此类推。描述位代表了值为 1 的二进制位的偏移位数,0
表示偏移 1 位、1 表示偏移 2 位、以此类推。
例如 0C,转换为 2 进制数后为:00001 100。类型位为 00001,是短间隙位字节组。类型位的值为 1,表示压缩了 2 个字节;描述位为 100,值为 4,表示第 5 位为 1。解压缩后,映射位为: 00000000 00010000,即该组表记录中,仅有第 12 条记录的索引字段值与当前键值匹配。
长间隙位组
当类型位的值等于 24,即 11000 时,该组字节为长间隙位字节,被压缩的二进制位中仅有 1 位为 1,其余为 0。描述位代表了值为 1 的二进制位的偏移位数,0 表示偏移 1 位、1 表示偏移 2 位、以此类推。在长间隙位分组中,控制字节后面会跟随 1 到多个偏移字节。每个偏移字节的第一位代表其后面是否还有偏移字节,1 表示有、0 表示无。所有偏移字节的第 2~7 位共同转换为 10 进制值,再加上 24,就是被压缩的间隙位字节数。
例如 C4 95 05,转换为 2 进制数后为:11000100 10010101 00000101。控制字节(11000100) 中,类型位值为 24,代表当前分区组长间隙位分组。第一个偏移字节的第一位为 1,表示后面还有偏移字节;第二个偏移字节的首位为 0,表示是最后一个偏移字节。偏移字节的 2~7 组成一个新数字:0000101 0010101,转换为 10 进制为:661,加 24 等于 685,即压缩了 685 个间隙字节。控制字节的描述位值为 4,即间隙字节的倒数第 5 位为 1,其余为 0。解压缩后:00000000 …(省略
683 个字节)… 00010000。
短间隙映射组
当类型位的值在 25~30 之间,即 11001~11110 时,该组字节为短间隙映射字节。此时,类型位的值还有另外一层含义:有多少间隙字节被压缩.它包含了 0~5 个被压缩的间隙字节,25 表示有
0 个字节、26 表示有 1 个字节、以此类推。间隙字节的二进制位全部为 0。描述位代表了控制字节后面跟随了多少个映射字节(1~8 个),0 表示跟随了 1 个、1 表示跟随了 2 个、以此类推。映射字节中的二进制位与相应的表记录一一对应,1 表示表记录中字段值与当前键值匹配、0 表示不匹配。
例如 E1 0A 12,控制字节转换为二进制后:11100001,类型位值为 28,表示该字节组为短间隙映射组,并且压缩了 3(28-25)个间隙字节。描述位的值为 1,表示控制字节后面跟随了 2 个映射字节,即 0A 12。解压缩后:00000000 00001010 00010010
长间隙映射组
当类型位的值等于 31,即 11111 时,该组字节为长间隙映射组字节。在长间隙映射组分组中, 控制字节后面会跟随 1 到多个偏移字节。每个偏移字节的第一位代表其后面是否还有偏移字节,1 表示有、0 表示无。所有偏移字节的第 2~7 位共同转换为 10 进制值,再加上 6,就是被压缩的间隙位字节数。间隙字节的二进制位全部为 0。描述位代表了偏移字节后面跟随了多少个映射字节
(1~8 个),0 表示跟随了 1 个、1 表示跟随了 2 个、以此类推。映射字节中的二进制位与相应的表记录一一对应,1 表示表记录中字段值与当前键值匹配、0 表示不匹配。
例如 F9 95 05 0A 12,控制字节转换为二进制后:11111001,类型位值为 31,表示该字节组为长间隙映射组,后面跟随了偏移字节。第一位是 95,即 10010101,首位为 1,表示后面还有偏移
字节,05,即 00000101,首位为 0,表示是最后一个偏移字节。偏移字节的 2~7 组成一个新数字: 0000101 0010101,转换为 10 进制为:661,加 6 等于 667,即压缩了 685 个间隙字节。描述位的值为 1,表示控制字节后面跟随了 2 个映射字节,即 0A 12。解压缩后:00000000 …(省略 665 个字
节)… 00010000 00001010 00010010。
回到我们的示例中。第一个字节是 02,转换为 2 进制后:00000 010。类型位为 0,表示为短间隙位组;描述位为 2,表示倒数第三位为 1、其余位为 0。解压缩后:00000100。即映射的表记录中,仅第三条的 STATUS 的值为“INVALID”。查询出实际值验证:





