3. db_file_multiblock_read_count
在多块(multiblock)读的情况下(例如,在全表扫描或快速全索引扫描时),数据库引擎使用的最大I/O大小取决于初始化参数db_file_multiblock_read_count和db_block_size的乘积。因此在多块读时最大的块数是用最大的I/O吞吐量除以一个块的大小得到的。换言之,对默认的块尺寸来说,初始化参数db_file_multiblock_read_count的值指定了一次最多可读取的数据块数目。这“仅仅”是一个理想的最大值,因为通常至少有下列三种情况可能导致一次多块读的数目少于此初始化参数指定的值。
⊙ 读段头时单块读。
⊙ 物理读不能跨越多个区(extent)。
⊙ 部分数据块已经在高速缓存(buffer cache)中,除非是直接读,否则不会从I/O子系统重新读取。
为了阐述清楚,下图展示了数据库中一个段(segment)的结构。
跟其他段一样,它由若干个区(extent)组成(在本例中,是两个区),每个区由若干个块组成(在本例中,是16个块)。第一个区的第一个块为段头块(segment header)。其中一些块(第4、9、10、19和21块)被缓存在高速缓存中。一个数据库进程对这个段执行一次串行的全扫描(并行全扫描有其特殊的行为,将在后面介绍),并不能通过一次单独的物理读就完成。即使初始化参数db_file_multiblock_read_count的值被设置为32或者更大也不能。
如果初始化参数db_file_multiblock_read_count被设置为8,将会造成如下的物理读过程。
⊙ 一个单块(single-block)读获取段头(块1)。
⊙ 一个多块(multiblock)读得到两个块(块2和3)。不能一次读更多的块因为块4被缓存。
⊙ 一个多块读读取4个块(从块5到块8)。不能读更多的块因为块9被缓存。
⊙ 一个多块读读取6个块(从块11到块16)。不能读更多的块因为块16是一个区的最后一个块,不能跨区读。
⊙ 一个多块读读取2个块(块17和块18)。不能读更多的块因为块19被缓存。
⊙ 一个单块读块取块20。不能读更多的块因为块21被缓存。
⊙ 一个多块读读取8个块(从块22到块29)。不能读更多的块因为初始化参数db_file_multiblock_read_count被设置为8。
⊙ 一个多块读读取3个块(从块30到块32)。
总之,这个进程共进行了两个单块读和六个多块读。平均每个多块读的块数约为4。平均每次读块的数目小于参数设置值8的事实,解释了Oracle在计算系统统计信息时引入变量mbrc的原因。
在此,了解查询优化器如何计算多块读的开销也是很重要的(比如,在全表扫描或快速全索引扫描时)。就像Wolfgang Breitling在他的文章“A Look Under the Hood of CBO: The 10053 Event”中指出的那样,当系统统计信息不可用时,这个开销可用下列公式来近似地计算。
无系统统计时多块读操作的 I/O 开销 当工作量系统统计信息可用时,I/O的开销不再只取决于初始化参数db_file_multiblock_read_count的值。而是用下列公式计算。
注意到变量mreadtim被sreadtim除是因为查询优化器通常是根据单块读来换算开销的。这点已在以前讨论过。
在公式中,如果是用非工作量统计信息,mbrc的值就用初始化参数db_file_multiblock_read_count的值来代替。
这意味着初始化参数db_file_multiblock_read_count仅在工作量统计信息不可用时,对多块读操作的开销计算有直接的影响。这也暗示着此初始化参数太大可能导致过度的全扫描或者至少低估了多块读操作的开销。进一步讲,这是工作量统计信息好于非工作量统计信息或者根本没有系统统计信息的又一个例子。
既然已经了解开销计算公式,关键在于如何找到公式中变量的合适值。最重要的是认识到多块读是一种和性能相关的特性。因此需要设置初始化参数db_file_multiblock_read_count以求获取最佳性能。为了达到这个目的,必须认识到并不是所有情况下值越大性能就越好。此外,将值设置为超过操作系统所能支持的物理I/O上限也是毫无意义的,这个上限是max I/O chunk size。对一个简单的全表扫描分别设置不同的参数值测试性能,可以获得这个参数对性能产生影响的有效信息,有助于发现它的最佳值。下面的PL/SQL代码块,参看脚本assess_dbfmbrc.sql,可用于此目的。
如你所见,做到这一点并不难,因为初始化参数db_file_multiblock_read_count是动态的并且可以在实例级和会话级修改。无论如何,千万小心不要在数据库、操作系统和I/O子系统层级缓存测试表的数据,否则将导致测试结果无效。避免它最容易的方法就是使用一张比系统中可用的最大缓存还要大的表来做测试。有一点要注意的是不要使用并行处理,因为通常数据库引擎通过不同的系统调用来执行并行全表扫描和串行全表扫描。 通过上面PL/SQL块的测试,显示出下列几种不同系统特性。
◎ 测试环境1:
随着I/O尺寸的增加,性能不断提升,一直到每次8左右。更大的值收效不大,甚至出现负增长。所以该多块读不易设置大于8
◎ 测试环境2:
一直到每次16块性能增长都很缓慢(每次增幅不超过10%)。当从16调到18时,性能骤然上升20%左右。超过18以后又表现平平。对这种系统,参数设置要避免小于18。
◎ 测试环境3:在参数达到8之前性能提升都很显著,在8到16之间,增长基本平缓。当从16到17时,出现16%的负增长。因此对这种系统来说,此参数值需要避免达到16以上。
◎ 测试环境4:系统的性能与此I/O的尺寸无关。
从Oracle 10gR2起,也可以指示数据库引擎自动配置初始化参数db_file_multiblock_read_count的值。使用这个特性的方法很简单,就是不手动设置此参数的值。
在10GR2,理论上,最大db_file_multiblock_read_count和系统IO能力应该有如下关系: Max(db_file_multiblock_read_count) = max I/O chunk size/db_block_size。其中max I/O chunk size跟操作系统有关,但是Oracle文档中也指出大多数操作系统上该值为1M。
像在公式中展示的那样,有两个条件选其最小的那个作为db_file_multiblock_read_count。
数据库引擎将其值设置为能保证每次物理读获取1MB的数据。同时,也将进行一种健全性检查,当在极端高会话数的系统中,高速缓存的大小相对于数据库支持的会话总数明显偏小时,调整减小此初始化参数的值,也就是每个会话的多块读总和不能大于高速缓存的大小。
如先前所讲,每次以最大I/O值进行物理读也不是总能带来好的性能,因此我们并不建议使用这个特性,更好的方式是通过一次次的测试找到最佳值,注意:同一个环境每次执行测试也会有区别。
注意如果在非工作量统计信息可用的情况下启用了自动配置, mbrc值不会被自动配置的db_file_multiblock_read_count值替代,而是被设置为8。
4. optimizer_dynamic_sampling
查询优化器过去一般都是把数据字典中的对象统计信息作为它评估的唯一依据。而通过使用动态采样,这种情况已经有了改观。事实上,有些统计信息可以在语句解析阶段动态地收集。这意味着为了收集额外的信息,还要对涉及的对象执行一些查询。遗憾的是,动态采样得到的统计信息既不存储在数据字典中也不存储在其他地方,真正重用它们的唯一方式是重用共享游标本身。
初始化参数optimizer_dynamic_sampling的值(或层级)指定动态采样的方式和时间。下表汇总了得到公认的值和它们的含义。
动态采样等级以及含义
注意这个参数的默认值由另一个初始化参数optimizer_features_ enable决定。
⊙ 如果optimizer_features_enable设置为10.0.0或更高,默认值为层级2。
⊙ 如果optimizer_features_enable设置为9.2.0,默认值为层级1。
⊙ 如果optimizer_features_enable设置为9.0.1或更低,动态采样特性则被禁用。
这个参数是动态的,可以使用SQL语句ALTER SYSTEM在实例级或者 SQL语句ALTER SESSION在会话级修改它的值。也可以通过提示dynamic_sampling在SQL语句级应用动态采样。这个提示有两种使用方式。
⊙ 为所有表设置采样层级:dynamic_sampling(level)。
⊙ 为一个指定表设置采样层级:dynamic_sampling(table_name level)。
* 这表示当动态采样特性通过初始化参数或通过未加表名(或别名)的提示而激活后,动态采样的数据块数。当使用一个带有表名(或别名)的提示的时候,数据块的数目,除了层级10,将通过如下的公式计算:32*2^(层级1)
这里用一些例子(摘录自运行在10.2.0.3版本的脚本dynamic_sampling_levels.sql)来解释在什么样的情况下层级1到层级4的动态采样会发生。测试用的表由下面的SQL语句创建。一开始,它们没有任何对象统计信息。注意表t_noidx和表t_idx之间的唯一区别是后者有主键。
第一组用来测试的查询如下,它们之间的细微区别在于第一个查询使用了表t_noidx,而第二个查询则使用了表t_idx。
如果参数的层级设置为1,只有第一个查询会进行动态采样,因为第二个查询所基于的表是有索引的。下面给出的是在我的测试数据库上为表t_noidx收集统计信息所用的递归查询。为了易于阅读,去掉了语句中的一些提示,且绑定变量也用实际值替换。需要注意的是在进行这个查询之前需要激活SQL跟踪功能。然后,我所需要做的便是检查产生的跟踪文件,找出执行了哪些递归SQL语句。
这里有几个十分重要的地方要注意。
⊙ 查询优化器计算
以上采样语句的4个列分别是
◎ 总记录行数
◎ 符合WHERE条件(id<19)的记录数
◎ 列id的唯一值数目
◎ NULL值的数目。
⊙ 查询中用到的变量必须是已知的。如果使用了绑定变量,查询优化器必须要能得到绑定的值,从而实施动态采样。
⊙ 通过SAMPLE子句进行采样,在我的数据库中表t_noidx有155个块,级别1的采样块大小是32块,所以采样百分比为20%(32/155)。
如果层级设置为2,那么上面的两个用于测试的查询语句都将进行采样操作。在这种情况下,只要没有对象统计信息都会进行采样。用来为两张表收集统计信息的递归查询和刚才所示的相同。而采样的百分比增加,因为在这个层级上,将抽取64块而非32块数据。此外,对表t_idx来说,会多出下面的这个递归查询,将有同样的效果。它的目的是在前一步的查询中用扫描索引来替代扫