#执行计划

# SQL与计划

执行计划是整个SQL语句执行过程的描述。一个执行计划的优秀与否,很大程度的决定了SQL执行的效率。

也正是因为一条SQL语句可以由不同的计划来进行执行,SQL调优才有了意义。

下面将结合示例来讲解,计划与调优具体之间的相关联系,阐明计划如何影响性能。

# 计划与算子

示例

EXPLAIN SELECT * FROM area WHERE area_no = 1;

PLAN_DESCRIPTION
----------------------------------------------------------------
SQL hash value: 3728302104
Optimizer: ADOPT_C

+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
| Id | Operation type                 | Name                 | Owner      | Rows     | Cost(%CPU)  | Partition info                 |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
|  0 | SELECT STATEMENT               |                      |            |          |             |                                |
|* 1 |  TABLE ACCESS FULL             | AREA                 | SALES      |         1|       13( 0)|                                |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+

Operation Information (identified by operation id):
---------------------------------------------------

   1 - Predicate : filter("AREA"."AREA_NO" = 1)

对于需要查询的table来说,能够通过多种不同的方式进行扫描(获取表中数据),这些不同的扫描方式就是不同的扫描算子。

因为存在不同的算子,才能使得一条SQL语句存在不同的执行计划,不同SQL语句的最适合、效率最高的算子也可能各不相同。

SQL调优的终极目标,就是在这些不同的执行计划中找到效率最高的那一个。

回到上述示例,table表在简单进行扫描时。选择何种算子最优,也不能一概而论。一种很流行的错误观点是无论任何扫描场景都应选择索引扫描,这是由于没有深层理解算子的实际执行过程所发生的误解。实际上,在需要回表时需采用table full scan,不需要回表时才采用index scan效率更高。

因此在进行调优之前,需要对算子进行深入了解。

# 算子效率评估

不同算子的效率既然不同,那就需要一套标准来评估各个算子性能的高低,这一套标准就是执行计划中展示出来的cost(执行代价)。

示例

EXPLAIN SELECT area_no FROM area WHERE area_no = 1;

PLAN_DESCRIPTION
----------------------------------------------------------------
SQL hash value: 1386453710
Optimizer: ADOPT_C

+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
| Id | Operation type                 | Name                 | Owner      | Rows     | Cost(%CPU)  | Partition info                 |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
|  0 | SELECT STATEMENT               |                      |            |          |             |                                |
|* 1 |  INDEX FAST FULL SCAN          | SYS_C_33             | SALES      |         1|        6( 0)|                                |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+

Operation Information (identified by operation id):
---------------------------------------------------

   1 - Predicate : filter("AREA"."AREA_NO" = 1)

通过该示例不难发现,正如上小节所说,在不需要回表时,采用index scan的代价小于table full scan。

一套能够反应算子真实代价的model,是生成高效计划的关键。但cost model也有可能失效,在一些较为复杂的语句上。cost model可能会表现的不够准确,此时需要人工介入去判断是否存在某些算子的cost评估不准确并视情况进行相应调整,规避不优秀的计划,例如使用hint的方式强行指定算子。

# 算子组合

计划往往不只是上述示例中所示,仅有少少的一层。当SQL变得复杂时,不仅需要考虑不同的算子,还要考虑如何选择多个算子的组合。

示例

EXPLAIN SELECT * FROM area a, branches b WHERE a.area_no = b.area_no;

PLAN_DESCRIPTION
----------------------------------------------------------------
SQL hash value: 1557742453
Optimizer: ADOPT_C

+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
| Id | Operation type                 | Name                 | Owner      | Rows     | Cost(%CPU)  | Partition info                 |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
|  0 | SELECT STATEMENT               |                      |            |          |             |                                |
|  1 |  NESTED LOOPS INNER            |                      |            |    100000|      211( 0)|                                |
|  2 |   TABLE ACCESS FULL            | BRANCHES             | SALES      |    100000|      132( 0)|                                |
|  3 |   TABLE ACCESS BY INDEX ROWID  | AREA                 | SALES      |          |             |                                |
|* 4 |    INDEX UNIQUE SCAN           | SYS_C_33             | SALES      |         1|       13( 0)|                                |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+

Operation Information (identified by operation id):
---------------------------------------------------

   4 - Predicate : access("A"."AREA_NO" = "B"."AREA_NO")

阐述算子组合方式的最好示例是Join,对于Join来说,单纯只考虑下层的scan算子是不够的,还需考虑不同表放置于Join算子中的位置(左侧或右侧),例如hash join中,建议将小表放在右边(build表)以降低建表的成本。

如何选择最优的算子组合与选择最优的算子一样至关重要,而选出最优的组合也是调优中最困难的部分,具体的调优规则可查阅SQL调优原理与规则

pdf-btn 下载文档
copy-btn 复制链接