承接国内外服务器租用托管、定制开发、网站代运营、网站seo优化托管接单、网站代更新,新老站点皆可!!咨询QQ:3787320601

Oracle CBO几种基本的查询转换详解

管理员 2023-07-04 08:33:50 互联网圈 6 ℃ 0 评论 42291字 收藏

Oracle CBO几种基本的查询转换详解

在履行计划的开发进程中,转换和选择有这个区别的任务;实际上,在一个查询进行完语法和权限检查后,首先产生通称为“查询转换”的步骤,这里会进行一系列查询块的转换,然后才是“优选”(优化器为了决定终究的履行计划而为区别的计划计算本钱从而选择终究的履行计划)。

我们知道查询块是以SELECT关键字辨别的,查询的书写方式决定了查询块之间的关系,各个查询块通常都是嵌在另外一个查询块中或以某种方式与其相联结;例如:

select * from employees where department_id in (select department_id from departments)

就是嵌套的查询块,不过它们的目的都是去探索如果改变查询写法是不是提供更好的查询计划。

这类查询转换的步骤对履行用户可以说是完全透明的,要知道转换器可能会在不改变查询结果集的情况下完全改写你的SQL语句结构,因此我们有必要重新评估自己的查询语句的心理预期,虽然这类转换通常来讲都是好事,为了取得更好更高效的履行计划。

我们现在来讨论一下几种基本的转换:

1.视图合并
2.子查询解嵌套
3.谓语前推
4.物化视图查询重写

一、视图合并

这类方式比较容易理解,它会将内嵌的视图展开成一个独立处理的查询块,或将其与查询剩余部份合并成一个总的履行计划,转换后的语句基本上不包括视图了。

视图合并通常产生在当外部查询块的谓语包括:

1,能够在另外一个查询块的索引中使用的列
2,能够在另外一个查询块的分区截断中所使用的列
3,在一个联结视图能够限制返回行数的条件

在这类查询器的转换下,视图其实不总会有自己的子查询计划,它会被预先分析并通常情况下与查询的其他部份合并以取得性能的提升,以下例。

SQL> set autotrace traceonly explain
— 进行视图合并
SQL> select * from EMPLOYEES a,
  2  (select DEPARTMENT_ID from EMPLOYEES) b_view
  3  where a.DEPARTMENT_ID = b_view.DEPARTMENT_ID(+)
  4  and a.SALARY > 3000;

Execution Plan
———————————————————-
Plan hash value: 1634680537

—————————————————————————————-
| Id  | Operation          | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————-
|   0 | SELECT STATEMENT   |                   |  3161 |   222K|     3   (0)| 00:00:01 |
|   1 |  NESTED LOOPS OUTER|                   |  3161 |   222K|     3   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| EMPLOYEES         |   103 |  7107 |     3   (0)| 00:00:01 |
|*  3 |   INDEX RANGE SCAN | EMP_DEPARTMENT_IX |    31 |    93 |     0   (0)| 00:00:01 |
—————————————————————————————-

Predicate Information (identified by operation id):
—————————————————

   2 – filter(“A”.”SALARY”>3000)
   3 – access(“A”.”DEPARTMENT_ID”=”DEPARTMENT_ID”(+))

— 使用NO_MERGE避免视图被重写
SQL> select * from EMPLOYEES a,
  2  (select /*+ NO_MERGE */DEPARTMENT_ID from EMPLOYEES) b_view
  3  where a.DEPARTMENT_ID = b_view.DEPARTMENT_ID(+)
  4  and a.SALARY > 3000;

Execution Plan
———————————————————-
Plan hash value: 1526679670

———————————————————————————–
| Id  | Operation             | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
———————————————————————————–
|   0 | SELECT STATEMENT      |           |  3161 |   253K|     7  (15)| 00:00:01 |
|*  1 |  HASH JOIN RIGHT OUTER|           |  3161 |   253K|     7  (15)| 00:00:01 |
|   2 |   VIEW                |           |   107 |  1391 |     3   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL  | EMPLOYEES |   107 |   321 |     3   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS FULL   | EMPLOYEES |   103 |  7107 |     3   (0)| 00:00:01 |
———————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   1 – access(“A”.”DEPARTMENT_ID”=”B_VIEW”.”DEPARTMENT_ID”(+))
   4 – filter(“A”.”SALARY”>3000)

出于某些情况,视图合并会被制止或限制,如果在一个查询块中使用了分析函数,聚合函数,,集合运算(如union,intersect,minux),order by子句,和rownum中的任何一种,这类情况都会产生;虽然如此,我们依然可使用/*+ MERGE(v) */提示来强迫使用视图合并,不过条件一定要保证返回的结果集是一致的!!!以下例:

SQL> set autotrace on
— 使用聚合函数avg致使视图合并失效
SQL> SELECT e1.last_name, e1.salary, v.avg_salary
  2  FROM hr.employees e1,
  3  (SELECT department_id, avg(salary) avg_salary
  4  FROM hr.employees e2
  5  GROUP BY department_id) v
  6  WHERE e1.department_id = v.department_id AND e1.salary > v.avg_salary;

Execution Plan
———————————————————-
Plan hash value: 2695105989

———————————————————————————-
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
———————————————————————————-
|   0 | SELECT STATEMENT     |           |    17 |   697 |     8  (25)| 00:00:01 |
|*  1 |  HASH JOIN           |           |    17 |   697 |     8  (25)| 00:00:01 |
|   2 |   VIEW               |           |    11 |   286 |     4  (25)| 00:00:01 |
|   3 |    HASH GROUP BY     |           |    11 |    77 |     4  (25)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| EMPLOYEES |   107 |   749 |     3   (0)| 00:00:01 |
|   5 |   TABLE ACCESS FULL  | EMPLOYEES |   107 |  1605 |     3   (0)| 00:00:01 |
———————————————————————————-

Predicate Information (identified by operation id):
—————————————————

   1 – access(“E1″.”DEPARTMENT_ID”=”V”.”DEPARTMENT_ID”)
       filter(“E1″.”SALARY”>”V”.”AVG_SALARY”)

–使用/*+ MERGE(v) */强迫进行视图合并
SQL> SELECT /*+ MERGE(v) */ e1.last_name, e1.salary, v.avg_salary
  2  FROM hr.employees e1,
  3  (SELECT department_id, avg(salary) avg_salary
  4  FROM hr.employees e2
  5  GROUP BY department_id) v
  6  WHERE e1.department_id = v.department_id AND e1.salary > v.avg_salary;

Execution Plan
———————————————————-
Plan hash value: 3553954154

———————————————————————————-
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
———————————————————————————-
|   0 | SELECT STATEMENT     |           |   165 |  5610 |     8  (25)| 00:00:01 |
|*  1 |  FILTER              |           |       |       |            |          |
|   2 |   HASH GROUP BY      |           |   165 |  5610 |     8  (25)| 00:00:01 |
|*  3 |    HASH JOIN         |           |  3296 |   109K|     7  (15)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| EMPLOYEES |   107 |  2889 |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| EMPLOYEES |   107 |   749 |     3   (0)| 00:00:01 |
———————————————————————————-

二、子查询解嵌套

最典型的就是子查询转变成表连接了,它和视图合并的主要区分就在于它的子查询位于where子句,由转换器进行解嵌套的检测。

下面便是一个子查询==>表连接的例子:

SQL> select employee_id, last_name, salary, department_id
  2  from hr.employees
  3  where department_id in
  4  (select department_id
  5  from hr.departments where location_id > 1700);

Execution Plan
———————————————————-
Plan hash value: 432925905

—————————————————————————————————
| Id  | Operation                     | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————————
|   0 | SELECT STATEMENT              |                   |    34 |   884 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                 |                   |       |       |            |          |
|   2 |   NESTED LOOPS                |                   |    34 |   884 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS BY INDEX ROWID| DEPARTMENTS       |     4 |    28 |     2   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | DEPT_LOCATION_IX  |     4 |       |     1   (0)| 00:00:01 |
|*  5 |    INDEX RANGE SCAN           | EMP_DEPARTMENT_IX |    10 |       |     0   (0)| 00:00:01 |
|   6 |   TABLE ACCESS BY INDEX ROWID | EMPLOYEES         |    10 |   190 |     1   (0)| 00:00:01 |
—————————————————————————————————

Predicate Information (identified by operation id):
—————————————————

   4 – access(“LOCATION_ID”>1700)
   5 – access(“DEPARTMENT_ID”=”DEPARTMENT_ID”)

— 使用/*+ NO_UNNEST */强迫为子查询单独生成履行计划
SQL> select employee_id, last_name, salary, department_id
  2  from hr.employees
  3  where department_id in
  4  (select /*+ NO_UNNEST */department_id
  5  from hr.departments where location_id > 1700);

Execution Plan
———————————————————-
Plan hash value: 4233807898

——————————————————————————————–
| Id  | Operation                    | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
——————————————————————————————–
|   0 | SELECT STATEMENT             |             |    10 |   190 |    14   (0)| 00:00:01 |
|*  1 |  FILTER                      |             |       |       |            |          |
|   2 |   TABLE ACCESS FULL          | EMPLOYEES   |   107 |  2033 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |     1 |     7 |     1   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | DEPT_ID_PK  |     1 |       |     0   (0)| 00:00:01 |
——————————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   1 – filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM “HR”.”DEPARTMENTS”
              “DEPARTMENTS” WHERE “DEPARTMENT_ID”=:B1 AND “LOCATION_ID”>1700))
   3 – filter(“LOCATION_ID”>1700)
   4 – access(“DEPARTMENT_ID”=:B1)

可以看到没有履行子查询解嵌套的查询只使用了FILTER来进行两张表的匹配,谓语信息第一步的查询也没有丝毫的改动,这便意味着对EMPLOYEES表中返回的107行的每行,都需要履行一次子查询。虽然在oracle中存在子查询缓存的优化,我们没法判断这两种计划的优劣,不过相比NESTED LOOPS,FILTER运算的劣势是很明显的。

如果包括相关子查询,解嵌套进程一般会将相关子查询转换成一个非嵌套视图,然后与主查询中的表x相联结,如:

SQL> select outer.employee_id, outer.last_name, outer.salary, outer.department_id
  2  from hr.employees outer
  3  where outer.salary >
  4  (select avg(inner.salary)
  5  from hr.employees inner
  6  where inner.department_id = outer.department_id);

Execution Plan
———————————————————-
Plan hash value: 2167610409

———————————————————————————-
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
———————————————————————————-
|   0 | SELECT STATEMENT     |           |    17 |   765 |     8  (25)| 00:00:01 |
|*  1 |  HASH JOIN           |           |    17 |   765 |     8  (25)| 00:00:01 |
|   2 |   VIEW               | VW_SQ_1   |    11 |   286 |     4  (25)| 00:00:01 |
|   3 |    HASH GROUP BY     |           |    11 |    77 |     4  (25)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| EMPLOYEES |   107 |   749 |     3   (0)| 00:00:01 |
|   5 |   TABLE ACCESS FULL  | EMPLOYEES |   107 |  2033 |     3   (0)| 00:00:01 |
———————————————————————————-

Predicate Information (identified by operation id):
—————————————————

   1 – access(“ITEM_1″=”OUTER”.”DEPARTMENT_ID”)
       filter(“OUTER”.”SALARY”>”AVG(INNER.SALARY)”)

上面的查询是将子查询转换成视图在与主查询进行hash join,转换后的查询其实像这样:

SQL> select outer.employee_id, outer.last_name, outer.salary, outer.department_id
  2  from hr.employees outer,
  3  (select department_id,avg(salary) avg_sal from hr.employees group by department_id) inner
  4  where inner.department_id = outer.department_id and outer.salary > inner.avg_sal;

其实这两个语句的履行计划也是一致

三、谓语前推

将谓词从内部查询块推动到一个不可合并的查询块中,这样可使得谓词条件更早的被选择,更早的过滤掉不需要的数据行,提高效力,一样可使用这类方式允许某些索引的使用。

— 谓语前推示例
SQL> set autotrace traceonly explain
SQL> SELECT e1.last_name, e1.salary, v.avg_salary
  2  FROM hr.employees e1,
  3  (SELECT department_id, avg(salary) avg_salary
  4  FROM hr.employees e2
  5  GROUP BY department_id) v
  6  WHERE e1.department_id = v.department_id
  7  AND e1.salary > v.avg_salary
  8  AND e1.department_id = 60;

Execution Plan
———————————————————-
Plan hash value: 3521487559

—————————————————————————————————–
| Id  | Operation                       | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————————–
|   0 | SELECT STATEMENT                |                   |     1 |    41 |     3   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                   |                   |       |       |            |          |
|   2 |   NESTED LOOPS                  |                   |     1 |    41 |     3   (0)| 00:00:01 |
|   3 |    VIEW                         |                   |     1 |    26 |     2   (0)| 00:00:01 |
|   4 |     HASH GROUP BY               |                   |     1 |     7 |     2   (0)| 00:00:01 |
|   5 |      TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     5 |    35 |     2   (0)| 00:00:01 |
|*  6 |       INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     5 |       |     1   (0)| 00:00:01 |
|*  7 |    INDEX RANGE SCAN             | EMP_DEPARTMENT_IX |     5 |       |     0   (0)| 00:00:01 |
|*  8 |   TABLE ACCESS BY INDEX ROWID   | EMPLOYEES         |     1 |    15 |     1   (0)| 00:00:01 |
—————————————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   6 – access(“DEPARTMENT_ID”=60)
   7 – access(“E1”.”DEPARTMENT_ID”=60)
   8 – filter(“E1″.”SALARY”>”V”.”AVG_SALARY”)

— 不进行谓语前推
SQL> SELECT e1.last_name, e1.salary, v.avg_salary
  2  FROM hr.employees e1,
  3  (SELECT department_id, avg(salary) avg_salary
  4  FROM hr.employees e2
  5  WHERE rownum > 1 — rownum等于同时使用了no_merge和no_push_pred提示,这会同时禁用视图合并和谓语前推
  6  GROUP BY department_id) v
  7  WHERE e1.department_id = v.department_id
  8  AND e1.salary > v.avg_salary
  9  AND e1.department_id = 60;

Execution Plan
———————————————————-
Plan hash value: 3834222907

————————————————————————————————–
| Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————————————–
|   0 | SELECT STATEMENT             |                   |     3 |   123 |     7  (29)| 00:00:01 |
|*  1 |  HASH JOIN                   |                   |     3 |   123 |     7  (29)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     5 |    75 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     5 |       |     1   (0)| 00:00:01 |
|*  4 |   VIEW                       |                   |    11 |   286 |     4  (25)| 00:00:01 |
|   5 |    HASH GROUP BY             |                   |    11 |    77 |     4  (25)| 00:00:01 |
|   6 |     COUNT                    |                   |       |       |            |          |
|*  7 |      FILTER                  |                   |       |       |            |          |
|   8 |       TABLE ACCESS FULL      | EMPLOYEES         |   107 |   749 |     3   (0)| 00:00:01 |
————————————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   1 – access(“E1″.”DEPARTMENT_ID”=”V”.”DEPARTMENT_ID”)
       filter(“E1″.”SALARY”>”V”.”AVG_SALARY”)
   3 – access(“E1”.”DEPARTMENT_ID”=60)
   4 – filter(“V”.”DEPARTMENT_ID”=60)
   7 – filter(ROWNUM>1)

比较上面的两个查询可以看到,在第一个查询中,DEPARTMENT_ID=60谓词被推动到视图v中履行了,这样就使得内部视图查询只需要取得部门号为60的平均薪水就能够了;而在第二个查询中则需要计算每一个部门的平均薪水,然后在与外部查询联结的时候使用DEPARTMENT_ID=60条件过滤,相对而言这里为了等待利用谓词条件,查询做了更多的工作。

四、使用物化视图进行查询重写

当为物化视图开启查询重写功能时,CBO优化器会评估相应查询对基表与物化视图的访问本钱,如果优化器认为该查询结果从物化视图中取得会更高效,那末就会其自动选择为物化视图来履行,否则则对基表生成查询计划。

或者来看栗子:

SQL> set autotrace traceonly explain
SQL> select DEPARTMENT_ID,count(EMPLOYEE_ID) from EMPLOYEES group by DEPARTMENT_ID;

Execution Plan
———————————————————-
Plan hash value: 1192169904

——————————————————————————–
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
——————————————————————————–
|   0 | SELECT STATEMENT   |           |    11 |    33 |     4  (25)| 00:00:01 |
|   1 |  HASH GROUP BY     |           |    11 |    33 |     4  (25)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMPLOYEES |   107 |   321 |     3   (0)| 00:00:01 |
——————————————————————————–

— 创建物化视图日志
SQL> create materialized view log on EMPLOYEES with sequence,
  2  rowid (EMPLOYEE_ID,DEPARTMENT_ID) including new values;

Materialized view log created.

— 创建物化视图,并指定查询重写功能
SQL> create materialized view mv_t
  2  build immediate refresh fast on commit
  3  enable query rewrite as
  4  select DEPARTMENT_ID,count(EMPLOYEE_ID) from EMPLOYEES group by DEPARTMENT_ID;

Materialized view created.

SQL> select DEPARTMENT_ID,count(EMPLOYEE_ID) from EMPLOYEES group by DEPARTMENT_ID;

Execution Plan
———————————————————-
Plan hash value: 1712400360

————————————————————————————-
| Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————————-
|   0 | SELECT STATEMENT             |      |    12 |   312 |     3   (0)| 00:00:01 |
|   1 |  MAT_VIEW REWRITE ACCESS FULL| MV_T |    12 |   312 |     3   (0)| 00:00:01 |
————————————————————————————-

Note
—–
   – dynamic sampling used for this statement (level=2)

可以看到在第二个查询中,虽然是指定的查询EMPLOYEES表,但是优化器自动选择了物化视图的履行路径,由于它判断出物化视图已记载当前查询需要的结果集数据了,直接访问物化视图会取得更高的效力。

值得注意的是,这里的物化视图查询重写是自动产生的,一样也能够使用/*+ rewrite(mv_t) */提示的方式强迫产生查询重写。

总结:

虽然优化器在用户透明的情况下改写了我们的查询结构,不过通常情况下这都是基于CBO优化模式下其判断较为高效的选择,这也是我们所期望的,同时为我们提供了一种学习方法,即在写SQL语句的进程中时刻斟酌优化器的作用。

文章来源:丸子建站

文章标题:Oracle CBO几种基本的查询转换详解

https://www.wanzijz.com/view/61316.html

X

截屏,微信识别二维码

微信号:weimawl

(点击微信号复制,添加好友)

打开微信