在 Oracle 数据库中,执行计划是树形结构,因此我们可以利用树形查询来定制执行计划。

    我们打开 PLSQL dev SQL 窗口,登录示例账户 Scott 并且运行如下 SQL。

    1. explain plan for select /*+ use_hash(a,dept) */ *
    2. from emp a, dept
    3. where a.deptno = dept.deptno
    4. and a.sal > 3000;

    然后执行下面的脚本,结果如图 3-2 所示。

    1. select case
    2. when (filter_predicates is not null or
    3. access_predicates is not null) then
    4. '*'
    5. else
    6. ' '
    7. end || id as "Id",
    8. lpad(' ', level) || operation || ' ' || options "Operation",
    9. object_name "Name",
    10. cardinality as "Rows",
    11. filter_predicates "Filter",
    12. access_predicates "Access"
    13. from plan_table
    14. start with id = 0
    15. connect by prior id = parent_id;

    定制执行计划 - 图1

    图 3-2

    我们曾在 1.2 节中提到,只有大表才会产生性能问题,因此可以将表的段大小添加到定制执行计划中,这样我们在用定制执行计划优化 SQL 的时候,可以很方便地知道表大小,从而更快地判断该步骤是否可能是性能瓶颈。下面脚本添加表的段大小以及索引段大小到定制执行计划中,结果如图 3-3 所示。

    1. select case
    2. when (filter_predicates is not null or
    3. access_predicates is not null) then
    4. '*'
    5. else
    6. ' '
    7. end || id as "Id",
    8. lpad(' ', level) || operation || ' ' || options "Operation",
    9. object_name "Name",
    10. cardinality as "Rows",
    11. b.size_mb "Size_Mb",
    12. filter_predicates "Filter",
    13. access_predicates "Access"
    14. from plan_table a,
    15. (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
    16. from dba_segments
    17. group by owner, segment_name) b
    18. where a.object_owner = b.owner(+)
    19. and a.object_name = b.segment_name(+)
    20. start with id = 0
    21. connect by prior id = parent_id;

    如图 3-3 所示,Size_Mb 显示表的段大小,单位是 MB。

    定制执行计划 - 图2

    图 3-3

    我们曾在 1.4 节中提到建立组合索引避免回表或者建立合适的组合索引减少回表次数。如果一个 SQL 只访问了某个表的极少部分列,那么我们可以将这些被访问的列联合在一起,从而建立组合索引。下面脚本将添加表的总字段数以及被访问字段数量到定制执行计划中,结果如图 3-4 所示。

    1. select case
    2. when access_predicates is not null or filter_predicates is not null then
    3. '*' || id
    4. else
    5. ' ' || id
    6. end as "Id",
    7. lpad(' ', level) || operation || ' ' || options "Operation",
    8. object_name "Name",
    9. cardinality "Rows",
    10. b.size_mb "Mb",
    11. case
    12. when object_type like '%TABLE%' then
    13. REGEXP_COUNT(a.projection, ']') || '/' || c.column_cnt
    14. end as "Column",
    15. access_predicates "Access",
    16. filter_predicates "Filter",
    17. case
    18. when object_type like '%TABLE%' then
    19. projection
    20. end as "Projection"
    21. from plan_table a,
    22. (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
    23. from dba_segments
    24. group by owner, segment_name) b,
    25. (select owner, table_name, count(*) column_cnt
    26. from dba_tab_cols
    27. group by owner, table_name) c
    28. where a.object_owner = b.owner(+)
    29. and a.object_name = b.segment_name(+)
    30. and a.object_owner = c.owner(+)
    31. and a.object_name = c.table_name(+)
    32. start with id = 0
    33. connect by prior id = parent_id;

    定制执行计划 - 图3

    图 3-4

    如图 3-4 中所示,Column 表示访问了表多少列/表一共有多少列Projection 显示了具体的访问列信息,限于书本宽度,图中没有显示 Projection 列信息。

    限于书本限制,定制执行计划本书不做进一步讨论,有兴趣的读者请自行添加其余定制信息到定制执行计划中。