1.1.1 原因
造成低效访问路径的主要原因有以下几个。
Ø 没有使用适合的访问结构(比如索引)。
Ø 使用了适合的访问结构,但是SQL语句的语法不允许查询优化器使用它。
Ø 表或索引是分区的,但是无法修剪。结果,所有的分区都需要访问。
Ø 表和/或索引没有适当的分区。
除了之前列表中的例子之外,还有另外两种情况会导致低效访问路径。
Ø 查询优化器做出错误判断时,可能是由于缺少对象统计信息,或是由于对象统计信息过旧,或由于使用了错误的查询优化器配置。这些没有放在上面的列表中,是因为默认对象统计信息必须是最近的并且查询优化器也是配置正确的。
Ø 当查询优化器自身出现问题时,比如,当出现内部bug或底层限制时。
1.1.2 解决方案
正如上一部分描述的那样,要高效执行SQL语句,目标就是最小化逻辑读,或者换句话说,使用访问路径访问更少的块。要达到这个目标,或许需要增加新的访问结构(比如,索引)或者改变物理设计(比如,对表或者它们的索引进行分区)。给定的SQL语句,会有很多访问结构和物理设计的组合。幸运的是,为了使选择更容易,可以根据选择性将SQL语句(或者更容易些,数据访问操作)分成以下两大类别:
Ø 弱选择性操作
Ø 强选择性操作
选择性很重要,因为访问结构和设计对弱选择性操作支持较好,对强选择性操作支持较差,反之亦然。然而,请注意这两个类别并没有明确的界限。相反,它是依赖于操作的,依赖于它处理的数据和数据存储的方式。
例如,数据分布和每块存储行数严重影响着性能。换句话说,并没有这样绝对的说法:选择率小于0.1必定是强选择性,而超过这个值就是弱选择性(或其他任何你想到的值)。尽管如此,实际上限制的范围通常是0.05~0.25。如图13-1所示,你只能确认接近0或1的值。
重点需要明白要决定一个操作的类别,与它返回的行数完全无关,只与选择性有关。例如,一个操作返回500000行与选择的访问路径完全无关。相反,一个操作的选择性为0.001,可以明确地把它放在强选择性类别中。
类别对于选择访问路径的类型很重要,它关联着高效的执行计划。图13-2概括地将选择性与访问路径关联在一起,通常来说这是最优化的方式。当使用合适的索引时,可以高效地执行强选择性操作。在本章稍后的部分,可以看到在一些场景中rowid访问或散列群集也可能会有帮助。另一方面,通过读取全表,可以高效执行弱选择性操作。在这两种可能性之间,分区表和散列群集扮演着重要的角色。
注意将数据存储在Exadata存储服务器上时,使用smart scan操作可以利用存储索引(storage index)来减少从磁盘物理读取的数据量。因此,一些平衡选择性的操作或者强选择性的操作,可以高效执行读取全表。我们不能控制存储索引,它们由Exadata存储服务器自动管理。
1.1.1.1 取回单行
这个实验使用access_structures_1.sq1脚本,目的是用取回一行数据所需要的逻辑读数与以下适当的访问结构进行对比:
Ø 带有主键 (primary key) 的堆表 (heap table)
Ø 索引组织表 (index_organized table)
Ø 主键作为群集键 (cluster key) 的单表散列群集 (single-table hash cluster)
注意 本章只介绍处理SQL语句期间如何利用不同类型的段(比如表、群集和索引)最小化逻辑读。可以在Oracle Database Concepts手册中找到它们的基本信息,尤其是“Schema Objects”这一章。
下面是用于实验的查询。请注意,id列是这个表的主键。存在值为6的行,并且rid变量保存着对应行的rowid:
select
* FROM sales
WHERE id =6;
select
* FROM sales
WHERE rowid = :rid;
由于逻辑读数与索引高度相关,实验在保存了10、10000和1000000行的表上执行。图13-3汇总了结果。它们阐述了以下四种主要事实。
Ø 对于所有的访问结构,都是通过rowid(显然,要读取这行保存的块,你无法做到比这个还少的逻辑读)执行单个逻辑读的。
Ø 对于堆表来说,至少需要两个逻辑读:一个用于索引,另一个用于表。随着行数的增加,索引高度的增加,逻辑读数也会增加。
Ø 访问索引组织表可以比访问堆表少一个逻辑读。
Ø 对于单表散列群集,不仅逻辑读数不依赖于行数,而且它总是导致单个逻辑读。
图13-3 不同的访问结构导致不同的逻辑读数
总之,要取回单行,一个“普通”的表加上索引是最低效的访问结构。然而,正如我在本章后续描述的那样,最常用的是“普通”的表,因为只有在特殊场景才可以利用其他访问结构。
1.1.1.1 取回多行
这个实验基于access_structures_1000.sql脚本,目的是用取回上千行数据所需的逻辑读数,与以下适当的访问结构进行对比。
Ø 没有索引的非分区表。
Ø 列表分区表。Prod_category列是分区列。
Ø 单表散列群集。Prod_category列是群集键。
Ø 在prod_category列上有索引的非分区表。对于这个实验,会测试表中的行分布在两个不同的段的情况(因此,存在不同的群集因子)。
测试的数据集包含918843行。下面的查询显示prod_category列的数据分布情况:
select prod_category, count(*),ratio_to_report(count(*))
over() AS selectivity
FROM sales
GROUP BY prod_category
ORDER BY count(*);
以下是用来测试的查询:
select sum(amount_sold) FROM sales WHERE
prod_category ='Hardware';
select sum(amount_sold) FROM sales WHERE
prod_category ='Photo';
select sum(amount_sold) FROM sales WHERE
prod_category ='Electronics';
select sum(amount_sold) FROM sales WHERE
prod_category ='Peripherals';
select sum(amount_sold) FROM sales WHERE
prod_category ='Software/Other';
select sum(amount_sold) FROM sales;
对于每一个查询,都会记录逻辑读数。图13-4汇总了结果,产生了以下四个要点。
Ø 没有索引的非分区表需要的逻辑读数与选择性无关。因此,它只在弱选择性时高效。
Ø 因为表已经根据prod_category列进行分区,所以列表分区表的单独一个分区需要的逻辑读数与选择性成正比。因此,在所有情况下,会实施最小逻辑读。
Ø 单表散列群集需要的逻辑读数仅跟选择性中等和高的值成正比(正如稍后会看到的,当选择性强时,散列群集会很有用。然而,在这个实验中,由于数据分布不均匀,它们处于劣势)。
Ø 通过索引读表需要的逻辑读数非常依赖于数据物理分布。因此,仅知道选择性不足以发现访问路径是否能高效处理数据。
图13-4 特定的访问路径仅对特定范围的选择性高效执行