解析Oracle数据扫描 Oracle SQL查询优化
(2)
2、 提高局部范围数据扫描执行性能的原理:
2.1查询条件的类别与作用:
当我们发起一个带有条件的查询SQL语句时,通常会赋予该语句多个查询条件,在这些查询条件中,通常会有一个或者几个查询条件会作为数据检索的发起者,这些作为发起者的查询条件称为“驱动查询条件”;同时那些不作为数据检索发起者的查询条件,在整个查询数据的过程中,只会起到过滤由驱动查询条件所决定的数据范围内数据的作用,这些查询条件就称为“过滤查询条件”。
由此可见驱动查询条件和过滤查询条件,一个起到查询发起者的作用,一个起到数据过滤者的作用,二者操作的目标数据集是不同的,驱动查询条件查询的是整体数据集,而过滤查询条件查询的是由驱动查询条件获取的结果集。由此可见驱动查询条件对于最终的查询语句的执行性能,起到了至关重要的作用。因为他们决定了最终查询结果的最大数据范围,以及能否最快的获取这个范围内的数据,而过滤查询条件只是从驱动查询条件决定的数据范围内进行数据过滤。当然我们也不能忽视通过过滤查询条件进行数据过滤对查询性能的影响,但是毕竟它没有对最终性能起到主要作用。
通常选择哪个查询条件作为驱动查询条件是由执行优化器和优化器运行模式(FIRST_ROWS或者ALL_ROWS)决定的。Oracle在决定驱动查询条件时,通常会考虑使用具有索引的列作为驱动查询条件,一般情况下Oracle会评估个查询条件上是否具备主键索引、唯一索引、B*Tree或其他类型索引、以及是否是使用等值条件的复合索引的手字段等,同时还会基于优化器模式通过成本估算方法评估各种索引的成本效能,尽力从中识别出最为高效的索引并将其作为驱动查询条件。如果查询优化器发现存在多个高效索引,那么理论上优化器会使用自己的策略决定出最佳执行成本的驱动查询条件;同时查询优化器还会识别出,哪些本应用于过滤条件的索引项,虽然不是那么高效,但是对驱动查询条件检索数据会提供很好的帮助,那么优化器会将这些索引与驱动查询条件合并执行,进而期望得到最好的查询性能。
但是通过查询优化器来决定驱动查询条件时,通常情况下都是没问题的,但是有些时候由于数据分布、数据对象统计信息过旧,以及与我们期望的数据扫描方式不符等问题,不得不需要我们人为去干预上述优化器的决策过程,其中最常用的干预手段就是使用Hints。这也从一个侧面说明,当今人工智能还无法取代真正的人类智能。
2.2提升范围扫描性能的原则:
有了上述查询条件类别的知识,我们就可以来讨论提升数据局部范围扫描性能的原则了。即使从常识出发来思考,也可以知道从较小的数据集中查询数据的执行速度会比较高效,从大范围的数据集中查询数据会比较低效。但是一旦融入了上述驱动查询条件和过滤查询条件的相互作用,在局部范围扫描时就会发现,从较大范围中查询数据的输出速度反倒高于从较小范围中查询数据。为什么会得到这样与我们常识相反的结果呢?我们通过下面的例子来说明这个问题。
这里有一个普通的SQL查询语句
Select * from order where ordno between 1and 1000 and custno like ‘TB%’;
假设在ordno和custno字段上都建有索引,同时知道满足ordno查询条件的数据记录有1000行,满足custno的数据记录有10行。很明显由于查询条件都具有索引,而且无需排序、分组、汇总等操作,因此该语句会使用局部范围数据扫描,那么就可能出现如下执行路径:
(1) 使用ordno作为驱动查询条件,custno作为过滤查询条件:
此时查询会使用ordno索引扫描数据满足条件的数据行,同时使用custno过滤条件来过滤满足条件的数据行,并将过滤出的符合条件的数据行填充到批量数组中,作为查询结果返回。但是问题是满足ordnobetween 1 and 1000条件的记录行有1000行,而满足custnolike ‘TB%’条件的记录行只有10行,那么用只有10行记录的数据去过滤拥有1000行记录的数据,并填充容纳最终结果的批量数组,可想而知批量数组很难被快速填满,即查询结果很难快速被返回,最坏的情况下可能要从头到尾的扫描驱动查询条件所决定的全部数据范围(1000条记录)后查询才能结束。
(2) 使用custno作为驱动查询条件,使用ordno作为过滤查询条件:
此时查询使用custno索引扫描满足条件的数据行,同时使用ordno过滤条件来过滤满足条件的数据行,并将过滤出的符合条件的数据行填充到批量数组中,作为查询结果返回。这时由于满足ordnobetween 1 and 1000条件的记录行有1000行,那么很容易从中找到能够匹配满足custnolike ‘TB%’条件的10行记录,因此批量数组很容易就能被填满,即使最坏的情况也最多只会扫描满足custnolike ‘TB%’条件的10行记录,因此此时查询结果接能够快速的返回。
为了改进查询性能,我们可以使用交换查询条件角色的方式,即交换驱动查询条件和过滤查询条件,因此我们可以使用下面的语句来改写上述查询:
Select /*+ index(order custno_inx) */ *
From order where ordnobetween 1 and 1000 and custno like ‘TB%’;
我们通过Hints来影响优化器行为,使优化器制定出从使用custno字段上的索引来发起查询,即将驱动查询条件变成custnolike ‘TB%’,将过滤查询条件变成ordno between 1 and 1000。这里使用了Hints,其实还可以使用一些措施使得ordno索引失效,也可以达到同样的目的,比如在ordno字段上使用一个对查询结果不会造成影响的函数,如rtrim(ordno)between1 and 1000,但这种方法存在弊端,它可能会影响优化器制定出合并使用custno和ordno共同来进行数据查询的执行计划。
从这个示例中我们可以看出提升局部范围扫描执行性能的策略有以下两点:
Ø 尽量缩减驱动查询条件的数据范围,以减少数据扫描量;
Ø 尽量扩大过滤查询条件的数据范围,以增加填满批量数组快速返回查询结果的概率;
由此我们可以得到下述局部范围扫描性能提升策略描述矩阵:
满足驱动查询条件的数据范围 | 满足过滤查询条件的数据范围 | 性能 | 措施 |
小 | 小 | 高 |
|
小 | 大 | 高 |
|
大 | 小 | 低 | 交换过滤条件的角色 |
大 | 大 | 高 |
|