视图合并(View Merge):当 SQL 语句中有内联视图(in-line view,from 后面的子查询),或者 SQL 语句中有用 create view 创建的视图,CBO 会尝试将内联视图/视图拆开,进行等价的改写,这个过程就叫作视图合并。如果没有发生视图合并,在执行计划中,我们可以看到 VIEW 关键字,而且视图/子查询会作为一个整体。如果发生了视图合并,那么视图/子查询就会被拆开,而且执行计划中视图/子查询部分就没有 VIEW 关键字。

    现有如下 SQL 及其执行计划(Oracle11.2.0.1)。

    1. select a.*, c.grade
    2. from (select ename, sal, a.deptno, b.dname
    3. from emp a, dept b
    4. where a.deptno = b.deptno) a,
    5. salgrade c
    6. where a.sal between c.losal and c.hisal;
    1. 14 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 3095952880
    5. ------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
    7. ------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 65 | 9 (23)|
    9. | 1 | NESTED LOOPS | | | | |
    10. | 2 | NESTED LOOPS | | 1 | 65 | 9 (23)|
    11. | 3 | MERGE JOIN | | 1 | 52 | 8 (25)|
    12. | 4 | SORT JOIN | | 5 | 195 | 4 (25)|
    13. | 5 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
    14. |* 6 | FILTER | | | | |
    15. |* 7 | SORT JOIN | | 14 | 182 | 4 (25)|
    16. | 8 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
    17. |* 9 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|
    18. | 10 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|
    19. ------------------------------------------------------------------------------
    20. Predicate Information (identified by operation id):
    21. ---------------------------------------------------
    22. 6 - filter("SAL"<="C"."HISAL")
    23. 7 - access("SAL">="C"."LOSAL")
    24. filter("SAL">="C"."LOSAL")
    25. 9 - access("A"."DEPTNO"="B"."DEPTNO")

    SQL 语句中有内联视图,但是执行计划中没有 VIEW 关键字,说明发生了视图合并。内联视图中 EMP 表是与 DEPT 表关联的,但是执行计划中,EMP 表是与 SALGRADE 先关联的,EMP 表与 SALGRADE 关联之后得到一个结果集,再与 DEPT 表进行的关联,这说明发生了视图合并之后,有可能会打乱视图/子查询中表的原本连接顺序。

    现在我们添加 HINT:no_merge(子查询别名/视图别名)禁止视图合并,再看执行计划。

    1. select /*+ no_merge(a) */
    2. a.*, c.grade
    3. from (select ename, sal, a.deptno, b.dname
    4. from emp a, dept b
    5. where a.deptno = b.deptno) a,
    6. salgrade c
    7. where a.sal between c.losal and c.hisal;
    1. 14 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 4110645763
    5. ----------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
    7. ----------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 81 | 11 (28)|
    9. | 1 | MERGE JOIN | | 1 | 81 | 11 (28)|
    10. | 2 | SORT JOIN | | 5 | 195 | 4 (25)|
    11. | 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
    12. |* 4 | FILTER | | | | |
    13. |* 5 | SORT JOIN | | 14 | 588 | 7 (29)|
    14. | 6 | VIEW | | 14 | 588 | 6 (17)|
    15. | 7 | MERGE JOIN | | 14 | 364 | 6 (17)|
    16. | 8 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
    17. | 9 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
    18. |* 10 | SORT JOIN | | 14 | 182 | 4 (25)|
    19. | 11 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
    20. ----------------------------------------------------------------------------------
    21. Predicate Information (identified by operation id):
    22. ---------------------------------------------------
    23. 4 - filter("A"."SAL"<="C"."HISAL")
    24. 5 - access("A"."SAL">="C"."LOSAL")
    25. filter("A"."SAL">="C"."LOSAL")
    26. 10 - access("A"."DEPTNO"="B"."DEPTNO")
    27. filter("A"."DEPTNO"="B"."DEPTNO")

    执行计划中有 VIEW 关键字,而且 EMP 是与 DEPT 进行关联的,这说明执行计划中没有发生视图合并。

    我们也可以直接在子查询里面添加 HINT:no_merge 禁止视图合并。

    1. select a.*, c.grade
    2. from (select /*+ no_merge */
    3. ename, sal, a.deptno, b.dname
    4. from emp a, dept b
    5. where a.deptno = b.deptno) a,
    6. salgrade c
    7. where a.sal between c.losal and c.hisal;
    1. 14 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 4110645763
    5. ----------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
    7. ----------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 81 | 11 (28)|
    9. | 1 | MERGE JOIN | | 1 | 81 | 11 (28)|
    10. | 2 | SORT JOIN | | 5 | 195 | 4 (25)|
    11. | 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
    12. |* 4 | FILTER | | | | |
    13. |* 5 | SORT JOIN | | 14 | 588 | 7 (29)|
    14. | 6 | VIEW | | 14 | 588 | 6 (17)|
    15. | 7 | MERGE JOIN | | 14 | 364 | 6 (17)|
    16. | 8 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
    17. | 9 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
    18. |* 10 | SORT JOIN | | 14 | 182 | 4 (25)|
    19. | 11 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
    20. ----------------------------------------------------------------------------------
    21. Predicate Information (identified by operation id):
    22. ---------------------------------------------------
    23. 4 - filter("A"."SAL"<="C"."HISAL")
    24. 5 - access("A"."SAL">="C"."LOSAL")
    25. filter("A"."SAL">="C"."LOSAL")
    26. 10 - access("A"."DEPTNO"="B"."DEPTNO")
    27. filter("A"."DEPTNO"="B"."DEPTNO")

    当视图/子查询中有多个表关联,发生视图合并之后一般会将视图/子查询内部表关联顺序打乱。

    大家可能遇到过类似案例,例如下面 SQL 所示。

    1. select ... from () a,() b where a.id=b.id;

    单独执行子查询 a,速度非常快,单独执行子查询 b,速度也非常快,但是把上面两个子查询组合在一起,速度反而很慢,这就是典型的视图合并引起的性能问题。遇到类似问题,我们可以添加 HINT:no_merge 禁止视图合并,也可以让子查询 a 与子查询 b 进行 HASH 连接,当子查询 a 与子查询 b 进行 HASH 连接之后,就不会发生视图合并了。

    1. 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 执行计划。

    1. select a.*, c.grade
    2. from (select ename, sal, a.deptno, b.dname
    3. from emp a, dept b
    4. where a.deptno = b.deptno
    5. union all
    6. select 'SMITH', 1600, 10, 'ACCOUNTING' from dual) a,
    7. salgrade c
    8. where a.sal between c.losal and c.hisal;
    1. 15 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 1428389312
    5. -----------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
    7. -----------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 81 | 13 (24)|
    9. | 1 | MERGE JOIN | | 1 | 81 | 13 (24)|
    10. | 2 | SORT JOIN | | 5 | 195 | 4 (25)|
    11. | 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
    12. |* 4 | FILTER | | | | |
    13. |* 5 | SORT JOIN | | 15 | 630 | 9 (23)|
    14. | 6 | VIEW | | 15 | 630 | 8 (13)|
    15. | 7 | UNION-ALL | | | | |
    16. | 8 | MERGE JOIN | | 14 | 364 | 6 (17)|
    17. | 9 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
    18. | 10 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
    19. |* 11 | SORT JOIN | | 14 | 182 | 4 (25)|
    20. | 12 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
    21. | 13 | FAST DUAL | | 1 | | 2 (0)|
    22. -----------------------------------------------------------------------------------
    23. Predicate Information (identified by operation id):
    24. ---------------------------------------------------
    25. 4 - filter("A"."SAL"<="C"."HISAL")
    26. 5 - access("A"."SAL">="C"."LOSAL")
    27. filter("A"."SAL">="C"."LOSAL")
    28. 11 - access("A"."DEPTNO"="B"."DEPTNO")
    29. filter("A"."DEPTNO"="B"."DEPTNO")

    从执行计划中我们可以看到,添加了 union all 之后,子查询被固化,没有发生视图合并。

    现在我们对 SQL 添加 rownum,查看 SQL 执行计划。

    1. select a.*, c.grade
    2. from (select ename, sal, a.deptno, b.dname
    3. from emp a, dept b
    4. where a.deptno = b.deptno
    5. and rownum >= 1) a,
    6. salgrade c
    7. where a.sal between c.losal and c.hisal;
    1. 14 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 819637296
    5. ------------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
    7. ------------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 72 | 11 (28)|
    9. | 1 | MERGE JOIN | | 1 | 72 | 11 (28)|
    10. | 2 | SORT JOIN | | 5 | 195 | 4 (25)|
    11. | 3 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 3 (0)|
    12. |* 4 | FILTER | | | | |
    13. |* 5 | SORT JOIN | | 14 | 462 | 7 (29)|
    14. | 6 | VIEW | | 14 | 462 | 6 (17)|
    15. | 7 | COUNT | | | | |
    16. |* 8 | FILTER | | | | |
    17. | 9 | MERGE JOIN | | 14 | 364 | 6 (17)|
    18. | 10 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)|
    19. | 11 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
    20. |* 12 | SORT JOIN | | 14 | 182 | 4 (25)|
    21. | 13 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)|
    22. ------------------------------------------------------------------------------------
    23. Predicate Information (identified by operation id):
    24. ---------------------------------------------------
    25. 4 - filter("A"."SAL"<="C"."HISAL")
    26. 5 - access("A"."SAL">="C"."LOSAL")
    27. filter("A"."SAL">="C"."LOSAL")
    28. 8 - filter(ROWNUM>=1)
    29. 12 - access("A"."DEPTNO"="B"."DEPTNO")
    30. filter("A"."DEPTNO"="B"."DEPTNO")

    从执行计划中我们可以看到,添加了 rownum 之后,子查询同样被固化,没有发生视图合并。