本文基于Oracle 12c优化器官方文档进行了翻译,原文链接如下:
The Oracle Optimizer Explain the Explain Plan
何为成本?
Oracle优化器是一个基于成本的优化器。为SQL语句选择的执行计划只是优化器考虑的众多备选执行计划之一。优化器总是选择它认为成本最低的执行计划,其中的成本表示该计划估计的资源使用量。成本越低,计划的效率就越高。优化器的成本模型考虑了查询时将使用的IO、CPU和网络资源。

整个计划的成本(在第0行中表示)和每个单独操作的成本都显示在执行计划中。成本不是可以调整或改变的东西。成本只是一个内部单位,仅用于执行计划之间的比较。
理解执行计划
为了确定优化器为SQL语句生成的执行计划是否是好的执行计划,我们首先需要了解优化器是如何确定执行计划的。我们应当能够查看执行计划并评估优化器在其估计或计算中是否犯了错误,从而导致生成了一个次优计划。评估执行计划包含以下几个方面:
- Cardinality(基数),每一步操作预估的数据行数
- Access method(访问方式),数据被访问的方式,通过表扫描或索引扫描
- Join method(连接方式),表连接的方式,如Nested Loop、Hash Join等
- Join type(连接类型),连接的类型,如outer、anti、semi等
- Join order(连接顺序),多表连接的先后顺序
- Partition pruning(分区裁剪),查询是否只访问了必要的分区?
- Parallel Execution(并行执行),在并行执行的情况下,计划中的每个操作都是并行执行的吗?是否使用了正确的数据重分配方法?
以下将对执行计划的每一个组成进行详细的讨论。
基数(Cardinality)
基数是每个操作将返回的预估行数。优化器根据一组复杂的公式确定每个操作的基数,这些公式同时使用表级和列级的统计信息作为输入(或动态采样派生的统计信息)。当一个表查询中只有一个等式谓词时(不存在直方图),优化器会使用最简单的公式。在这种情况下,优化器会假设数据分布是均匀统一的,并通过将表中的总行数除以where子句谓词中使用的列中的不同值的数量来计算查询时的基数。
SELECT employee_id, last_name, job_id
FROM hr.employees
WHERE job_id = 'AD_VP';
Select plan_table_output
From
table(dbms_xplan.display_cursor(null,null,'TYPICAL');
如下图图9所示,HR Schema中对employees表进行了查询,该表有107行:

job_id列有19个不同的值,因此优化器预测该语句的基数为107/19或5.6行,DBMS_XPLAN将其四舍五入为6行。
对于基数估计值来说,最重要的是要尽可能准确,因为基数估计值会影响执行计划的所有方面,从访问方式到连接顺序。然而,即使基本表和列的统计信息是最新的,一些其他因素也可能导致不正确的基数估计。这些因素包括:
- 数据倾斜程度
- 单个表上的多个单列谓词
- 函数封装了Where子句谓词中的列
- 语句的复杂度
在前文的示例中,EMPLOYEES表中存在数据倾斜。每个job_id对应的雇员数量并不平均。job_id为‘AD_VP’的雇员在employees表中的实际行数为2,只有优化器最初估计(6)的1/3。为了准确地反映数据倾斜,JOB_ID列上需要一个直方图。直方图的存在改变了优化器用来确定基数估计值的公式。
默认情况下,Oracle会根据列的统计信息和数据倾斜的存在,自动确定需要创建直方图的列。如果想要手动创建直方图,可以使用以下命令。
SQL> exec DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES', method_opt=>'FOR COLUMNS SIZE 254 JOB_ID');
有了JOB_ID列的直方图,优化器就可以确定从EMPLOYEES表返回的正确行数,如图10所示。

在上面的示例中,虽然更准确的预估基数并没有导致实际执行计划发生变化;但在很多情况下,基数的变化是肯定会导致执行计划的变更的。
确定正确的基数
要确定优化器预估的基数是否正确(或接近正确数值),我们可以通过在每一个查询中涉及到的表进行包含相应Where谓词条件的select count(*)操作来确定正确的基数。对于之前使用的简单示例:
SQL> SELECT COUNT(*)
2 FROM hr.employees
3 WHERE job_id=’AD_VP’;
COUNT(*)
-------------
2
此外,我们也可以使用SQL语句中的GATHER_PLAN_STATISTICS提示来自动收集更全面的运行时统计信息。此提示记录语句执行时每个操作的实际基数(返回的行数)。
通过使用DBMS_XPLA.DISPLAY_CURSOR,并将格式参数设置为‘ALLSTATS LAST’,我们可以在执行计划中显示此执行时(或运行时)基数。执行计划中将出现一个名为A-Rows的附加列,它表示实际返回的行。
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */
2 employee_id,last_name,job_id
3 FROM employees
4 WHERE job_id='AD_VP';
SQL> SELECT plan_table_output
2 FROM table(DBMS_XPLAN.DISPLAY_CURSOR
3 (FORMAT=>'ALLSTATS LAST'));

有一点需要注意:使用GATHER_PLAN_STATISTICS提示会影响SQL语句的执行时间,因此应该仅将其用于分析目的。若Oracle初始化时init.ora配置文件中的STATISTICS_LEVEL值被设置为ALL,那我们就不需要再使用此提示。此时,SQL监视功能(无论是在Oracle Enterprise Manager中还是使用PL/SQL)将始终显示A-Rows列信息,且不会增加额外的SQL语句开销,如图12所示。SQL监控是“调优和诊断包”的一部分,需要额外的授权。

网友评论