视图合并(View Merge):当 SQL 语句中有内联视图(in-line view,from 后面的子查询),或者 SQL 语句中有用 create view 创建的视图,CBO 会尝试将内联视图/视图拆开,进行等价的改写,这个过程就叫作视图合并。如果没有发生视图合并,在执行计划中,我们可以看到 VIEW 关键字,而且视图/子查询会作为一个整体。如果发生了视图合并,那么视图/子查询就会被拆开,而且执行计划中视图/子查询部分就没有 VIEW 关键字。
现有如下 SQL 及其执行计划(Oracle11.2.0.1)。
select a.*, c.gradefrom (select ename, sal, a.deptno, b.dnamefrom emp a, dept bwhere a.deptno = b.deptno) a,salgrade cwhere 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.gradefrom (select ename, sal, a.deptno, b.dnamefrom emp a, dept bwhere a.deptno = b.deptno) a,salgrade cwhere 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.gradefrom (select /*+ no_merge */ename, sal, a.deptno, b.dnamefrom emp a, dept bwhere a.deptno = b.deptno) a,salgrade cwhere 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.gradefrom (select ename, sal, a.deptno, b.dnamefrom emp a, dept bwhere a.deptno = b.deptnounion allselect 'SMITH', 1600, 10, 'ACCOUNTING' from dual) a,salgrade cwhere 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.gradefrom (select ename, sal, a.deptno, b.dnamefrom emp a, dept bwhere a.deptno = b.deptnoand rownum >= 1) a,salgrade cwhere 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 之后,子查询同样被固化,没有发生视图合并。
