视图合并(View Merge):当 SQL 语句中有内联视图(in-line view,from 后面的子查询),或者 SQL 语句中有用 create view 创建的视图,CBO 会尝试将内联视图/视图拆开,进行等价的改写,这个过程就叫作视图合并。如果没有发生视图合并,在执行计划中,我们可以看到 VIEW 关键字,而且视图/子查询会作为一个整体。如果发生了视图合并,那么视图/子查询就会被拆开,而且执行计划中视图/子查询部分就没有 VIEW 关键字。
现有如下 SQL 及其执行计划(Oracle11.2.0.1)。
select a.*, c.grade
from (select ename, sal, a.deptno, b.dname
from emp a, dept b
where a.deptno = b.deptno) a,
salgrade c
where a.sal between c.losal and c.hisal;
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3095952880
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 65 | 9 (23)|
| 1 | NESTED LOOPS | | | | |
| 2 | NESTED LOOPS | | 1 | 65 | 9 (23)|
| 3 | MERGE JOIN | | 1 | 52 | 8 (25)|
| 4 | SORT JOIN | | 5 | 195 | 4 (25)|
| 5 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
|* 6 | FILTER | | | | |
|* 7 | SORT JOIN | | 14 | 182 | 4 (25)|
| 8 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
|* 9 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|
| 10 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - filter("SAL"<="C"."HISAL")
7 - access("SAL">="C"."LOSAL")
filter("SAL">="C"."LOSAL")
9 - access("A"."DEPTNO"="B"."DEPTNO")
SQL 语句中有内联视图,但是执行计划中没有 VIEW 关键字,说明发生了视图合并。内联视图中 EMP 表是与 DEPT 表关联的,但是执行计划中,EMP 表是与 SALGRADE 先关联的,EMP 表与 SALGRADE 关联之后得到一个结果集,再与 DEPT 表进行的关联,这说明发生了视图合并之后,有可能会打乱视图/子查询中表的原本连接顺序。
现在我们添加 HINT:no_merge(子查询别名/视图别名)
禁止视图合并,再看执行计划。
select /*+ no_merge(a) */
a.*, c.grade
from (select ename, sal, a.deptno, b.dname
from emp a, dept b
where a.deptno = b.deptno) a,
salgrade c
where a.sal between c.losal and c.hisal;
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4110645763
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 81 | 11 (28)|
| 1 | MERGE JOIN | | 1 | 81 | 11 (28)|
| 2 | SORT JOIN | | 5 | 195 | 4 (25)|
| 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
|* 4 | FILTER | | | | |
|* 5 | SORT JOIN | | 14 | 588 | 7 (29)|
| 6 | VIEW | | 14 | 588 | 6 (17)|
| 7 | MERGE JOIN | | 14 | 364 | 6 (17)|
| 8 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
| 9 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
|* 10 | SORT JOIN | | 14 | 182 | 4 (25)|
| 11 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A"."SAL"<="C"."HISAL")
5 - access("A"."SAL">="C"."LOSAL")
filter("A"."SAL">="C"."LOSAL")
10 - access("A"."DEPTNO"="B"."DEPTNO")
filter("A"."DEPTNO"="B"."DEPTNO")
执行计划中有 VIEW 关键字,而且 EMP 是与 DEPT 进行关联的,这说明执行计划中没有发生视图合并。
我们也可以直接在子查询里面添加 HINT:no_merge
禁止视图合并。
select a.*, c.grade
from (select /*+ no_merge */
ename, sal, a.deptno, b.dname
from emp a, dept b
where a.deptno = b.deptno) a,
salgrade c
where a.sal between c.losal and c.hisal;
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4110645763
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 81 | 11 (28)|
| 1 | MERGE JOIN | | 1 | 81 | 11 (28)|
| 2 | SORT JOIN | | 5 | 195 | 4 (25)|
| 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
|* 4 | FILTER | | | | |
|* 5 | SORT JOIN | | 14 | 588 | 7 (29)|
| 6 | VIEW | | 14 | 588 | 6 (17)|
| 7 | MERGE JOIN | | 14 | 364 | 6 (17)|
| 8 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
| 9 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
|* 10 | SORT JOIN | | 14 | 182 | 4 (25)|
| 11 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A"."SAL"<="C"."HISAL")
5 - access("A"."SAL">="C"."LOSAL")
filter("A"."SAL">="C"."LOSAL")
10 - access("A"."DEPTNO"="B"."DEPTNO")
filter("A"."DEPTNO"="B"."DEPTNO")
当视图/子查询中有多个表关联,发生视图合并之后一般会将视图/子查询内部表关联顺序打乱。
大家可能遇到过类似案例,例如下面 SQL 所示。
select ... from () a,() b where a.id=b.id;
单独执行子查询 a,速度非常快,单独执行子查询 b,速度也非常快,但是把上面两个子查询组合在一起,速度反而很慢,这就是典型的视图合并引起的性能问题。遇到类似问题,我们可以添加 HINT:no_merge 禁止视图合并,也可以让子查询 a 与子查询 b 进行 HASH 连接,当子查询 a 与子查询 b 进行 HASH 连接之后,就不会发生视图合并了。
select /*+ use_hash(a,b) */ ... from () a,() b where a.id=b.id;
为什么让子查询 a 与子查询 b 进行 HASH 连接能使 SQL 变快呢?大家再回忆一下 HASH 连接的算法,嵌套循环会传值(驱动表传值给被驱动表,通过连接列),HASH 连接不会传值。因为 HASH 连接不传值,所以当子查询 a 与子查询 b 进行 HASH 连接之后,会自动地把子查询 a 与子查询 b 作为一个整体。
与子查询非嵌套一样,当视图中有固化子查询关键字的时候,就不能发生视图合并。
固化子查询的关键字包括 union、union all、start with connect by、rownum、cube、rollup。
现在我们对示例 SQL 添加 union all,查看 SQL 执行计划。
select a.*, c.grade
from (select ename, sal, a.deptno, b.dname
from emp a, dept b
where a.deptno = b.deptno
union all
select 'SMITH', 1600, 10, 'ACCOUNTING' from dual) a,
salgrade c
where a.sal between c.losal and c.hisal;
15 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1428389312
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 81 | 13 (24)|
| 1 | MERGE JOIN | | 1 | 81 | 13 (24)|
| 2 | SORT JOIN | | 5 | 195 | 4 (25)|
| 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
|* 4 | FILTER | | | | |
|* 5 | SORT JOIN | | 15 | 630 | 9 (23)|
| 6 | VIEW | | 15 | 630 | 8 (13)|
| 7 | UNION-ALL | | | | |
| 8 | MERGE JOIN | | 14 | 364 | 6 (17)|
| 9 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
| 10 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
|* 11 | SORT JOIN | | 14 | 182 | 4 (25)|
| 12 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
| 13 | FAST DUAL | | 1 | | 2 (0)|
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A"."SAL"<="C"."HISAL")
5 - access("A"."SAL">="C"."LOSAL")
filter("A"."SAL">="C"."LOSAL")
11 - access("A"."DEPTNO"="B"."DEPTNO")
filter("A"."DEPTNO"="B"."DEPTNO")
从执行计划中我们可以看到,添加了 union all 之后,子查询被固化,没有发生视图合并。
现在我们对 SQL 添加 rownum,查看 SQL 执行计划。
select a.*, c.grade
from (select ename, sal, a.deptno, b.dname
from emp a, dept b
where a.deptno = b.deptno
and rownum >= 1) a,
salgrade c
where a.sal between c.losal and c.hisal;
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 819637296
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 72 | 11 (28)|
| 1 | MERGE JOIN | | 1 | 72 | 11 (28)|
| 2 | SORT JOIN | | 5 | 195 | 4 (25)|
| 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
|* 4 | FILTER | | | | |
|* 5 | SORT JOIN | | 14 | 462 | 7 (29)|
| 6 | VIEW | | 14 | 462 | 6 (17)|
| 7 | COUNT | | | | |
|* 8 | FILTER | | | | |
| 9 | MERGE JOIN | | 14 | 364 | 6 (17)|
| 10 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
| 11 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
|* 12 | SORT JOIN | | 14 | 182 | 4 (25)|
| 13 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A"."SAL"<="C"."HISAL")
5 - access("A"."SAL">="C"."LOSAL")
filter("A"."SAL">="C"."LOSAL")
8 - filter(ROWNUM>=1)
12 - access("A"."DEPTNO"="B"."DEPTNO")
filter("A"."DEPTNO"="B"."DEPTNO")
从执行计划中我们可以看到,添加了 rownum 之后,子查询同样被固化,没有发生视图合并。