
1.4 串行直接路径算法
传统的数据读取方式是先读取磁盘,然后把数据加载到数据库服务器SGA中的BufferCache里。这样其他的服务进程就可以共享BufferCache中的这些数据,无须再访问缓慢的磁盘读取来完成。
直接路径读取数据块方式,是指服务器进程直接读取数据文件,而不会将数据块缓存到BufferCache中。这种方式读取的数据块会加载到服务器进程的PGA内存中,不会进入BufferCache中。
在Oracle 11g之前,直接路径读取主要用于并行查询中;而从Oracle 11g之后,直接路径读取方式不仅可用于并行查询,在符合某些条件后,串行的全表扫描也可以利用直接路径读取方式来完成。
1.4.1 _small_table_threshold参数
在Oracle 11g版本中,如果一条SQL语句进行全表扫描或快速全索引扫描,当表或索引的数据块数量超过_small_table_threshold参数值时,Oracle有可能会使用直接路径读取代替以前的BufferCache读取方式。
隐含参数_small_table_threshold用来界定“小表”的临界,当某张表的数据块个数小于该隐含参数值的,则被认为是“小表”。
对于大表,Oracle认为执行直接路径读取的效率会比BufferCache读取方式高;而对于“小表”,通过BufferCache读取效率更高。隐含参数_small_table_threshold的默认值为_db_block_buffers参数值的2%,单位为数据块的个数。
下面通过示例验证隐含参数_small_table_threshold与_db_block_buffers的关系,见代码清单1.19。
代码清单1.19 验证隐含参数_small_table_threshold与_db_block_buffers的关系

首先获取当前数据库_small_table_threshold隐含参数的值和当前数据库BufferCache中的数据块个数。
隐含参数_small_table_threshold的大小正好满足计算公式:
_small_table_threshold=2%∗_db_block_buffers=2%∗91140=1822.8=~1822
从以上代码输出可以看出,当前数据库小表的阈值基本为_db_block_buffers参数值的2%,也印证了这一隐含参数与BufferCache的关系。在Oracle 9i以前,设置db_block_buffers参数值,即间接设置了BufferCache大小;而从Oracle 9i开始,db_block_buffers变成了隐含参数,通过直接设置db_cache_size参数值,就可以间接得到隐含参数_db_block_buffers的值,db_cache_size参数值近似于_db_block_buffers隐含参数值∗db_block_size参数值。
1.4.2 _very_large_object_threshold参数
_very_large_object_threshold是Oracle 11g版本中新引入的隐含参数,主要用来定义Oracle数据库中的“特大表”。该参数的默认值为500,单位为%。换句话说,“特大表”的大小是BufferCache的500%,也就是5倍。
在互联网上广泛流传一种说法,隐含参数_very_large_object_threshold的单位是MB,默认是500MB。这种说法基本上是错误的。

当一张表的大小超过5倍的BufferCache时,则这张表被定义为“特大表”。
1.4.3 串行Direct Path Read算法
当数据库版本大于11.2.0.3时,串行直接路径读取的算法可用如图1.8来表示。

图1.8 串行直接路径读取算法流程图
串行直接路径读取算法可以简要概括如下。
■ 如果表的数据块数量小于“小表”,那么会使用内存读。
■ 如果表的数据块数量大于“特大表”,那么会使用直接路径读。
■ 如果表的数据块数量在“小表”与“特大表”之间,并且没有使用压缩,则根据已经缓存的数据块或脏数据块的比例决定采用哪种读取方式。如果对象已经缓存的数据块或脏数据块比例大于50%,会使用内存读;如果缓存的数据块或脏数据块的比例小于50%,则会使用直接路径读。
■ 如果表的数据块数量在“小表”与“特大表”之间,并且使用了压缩,则根据已经缓存的数据块或脏数据块的比例决定是采用哪种读取方式。如果对象缓存的数据块或脏数据块的比例大于95%,会使用内存读;当缓存的数据块或脏数据块的比例小于95%,则会使用直接路径读。
下面通过示例验证“小表”和“特大表”不开启并行的情况下,能否进行直接路径读取,见代码清单1.20。
代码清单1.20 验证“小表”和“特大表”能否进行直接路径读取(1)

获取当前数据库中影响大、小表定义的相关参数,根据前面讲述的大、小表的计算公式可知,数据块少于532个的表称之为“小表”,数据块多于133000个的表称之为“特大表”(26600×5)。
代码清单1.20 验证“小表”和“特大表”能否进行直接路径读取(2)


从以上代码输出可以看出,在test用户下创建了small、middle和big 3张测试表,并收集了这3张测试表的统计信息。test.small表中有100个数据块,而test.big表中有150000个数据块。当访问test.small表时,由于其数据块个数小于“小表”的阈值,所以没有进行直接路径读取;而当访问test.big表时,由于其数据块个数大于“特大表”的阈值,所以会进行直接路径读取,即使SQL语句中没有指定并行。
这里大家是否注意到一个问题,那就是所谓“小表”“特大表”的数据块个数是来自于统计信息,而不是真实数据块的使用个数。先留个悬念,后面章节会进行讲解。
继续通过下面的示例来验证当表对象介于“特大表”与“小表”之间时,数据块在内存中缓存的比例对直接路径读取的影响。所有测试用例继续沿用上面的测试表,测试过程见代码清单1.21。
代码清单1.21 验证介于“小表”与“特大表”之间的表直接路径读取(1)

从以上的代码输出可以看出,test.middle表中共有20000个数据块,其大小介于“特大表”与“小表”之间。首先通过检查可知,test.middle测试表在BufferCache中缓存了19709个数据块,也就是有96%左右的数据块缓存在BufferCache中。接着调用[NSMTIO]事件,分析SQL语句在内存读取和直接路径读取之间如何选择。
下面直接查看[NSMTIO]事件生成的日志内容,具体如下。
代码清单1.21 验证介于“小表”与“特大表”之间的表直接路径读取(2)

从[NSMTIO]事件的内容可以看出,该表的大小介于MTT与VLOT之间,也即介于“小表”与“特大表”之间。此时进行数据访问,则需要进行成本比较,来判断是进行直接路径读取。还是进行内存读取。由于总共才20000个数据块,已经缓存了19709个数据块,所以最终选择了CACHE_READ,也即内存读,而没有选择直接路径读取。
如果清空内存中已经缓存的相应数据块,对扫描方式的选择是否会产生影响?接着看下面的内容。
代码清单1.21 验证介于“小表”与“特大表”之间的表直接路径读取(3)

从以上代码输出可以看出,BufferCache中test.middle表相关的缓存已经全部被清除。接着再次调用[NSMTIO]事件来分析执行相同的SQL语句时,扫描方式是否会发生变化。
此时[NSMTIO]事件生成的日志文件如下。
代码清单1.21 验证介于“小表”与“特大表”之间的表直接路径读取(4)

清空BufferCache缓存,同时执行相同SQL语句后,从生成的[NSMTIO]事件内容可以看出,该表的大小介于MTT与VLOT之间,也即介于“小表”与“特大表”之间;该表共有20000个数据块,已经缓存了0个;最终选择了DirectRead,也即直接路径读取。
注意:①随着数据库版本的不断更新,串行直接路径读取的算法也在不断地发生变化;②[NSMTIO]事件中的VLOT表示_very_large_object_threshold,STT表示_small_table_threshold,而MTT的值为5∗STT,也即5倍的_small_table_threshold,但在11gR2及以上版本中已经废弃,所以在高版本的数据库中,设置[NSMTIO]事件进行分析时,会给人一种错觉。
1.4.4 _direct_read_decision_statistics_driven参数
有一点不知大家注意到了没有?在前面验证串行直接路径读取算法时使用的示例中,表的数据块个数取自于dba_tables或user_tables视图,而不是dba_segments或user_segments视图,这说明表的数据块个数多少并不是指表的真实数据块数量,而是指表的统计信息中的数据块数量。
从统计信息中获取数据块的数量,还是从真实的数据段头中获取数据块的数量,其实是由隐含参数_direct_read_decision_statistics_driven来决定的。此参数默认值为true,表示是从表的统计信息中获取数据块的数量来决定是否使用直接路径读取。如果将此隐含参数设置为false,那么就会由段头信息中获取实际的数据块数量来决定是否使用直接路径读取。
下面通过示例来验证,见代码清单1.22。
代码清单1.22 隐含参数_direct_read_decision_statistics_driven对直接路径读取的影响(1)

以100%的比例来收集test.middle测试表的统计信息,可以看出该表的真实数据块为19891个。在此手动修改该测试表的统计信息,将数据块个数修改为10,变相地认为这是一张非常小的表,同时将_direct_read_decision_statistics_driven隐含参数设置为true,获取SQL语句的[NSMTIO]事件。
代码清单1.22 隐含参数_direct_read_decision_statistics_driven对直接路径读取的影响(2)

从该SQL语句的[NSMTIO]事件跟踪日志可以看出,该对象的数据块只有10个,远远小于“小表”的阈值,所以最终只能NoDirectRead,也即只能进行内存读。
代码清单1.22 隐含参数_direct_read_decision_statistics_driven对直接路径读取的影响(3)

再次清空测试环境,访问相同的SQL语句,仅仅是将隐含参数_direct_read_decision_statistics_driven设置成false,也即不从统计信息中获取数据块个数,而从数据对象的段头中获取真实的数据块个数,同时获取SQL语句的[NSMTIO]事件。
代码清单1.22 隐含参数_direct_read_decision_statistics_driven对直接路径读取的影响(4)

从该SQL语句的[NSMTIO]事件跟踪日志可以看出,该对象介于“小表”与“特大表”之间,其数据块有19891个,缓存在内存中的数据块只有1个,所以最终选择了DirectRead,也即进行直接路径读取。