生成和显示SQL语句的执行计划,是大多数DBA、SQL开发人员以及性能优化专家经常做的工作,因为执行计划能够提供SQL语句性能相关的信息。执行计划解释了SQL语句执行的详细过程,这个过程记录了一系列的数据库操作以及每个操作涉及到的数据行数和生成的数据行数。优化器使用查询转换和物理优化技术决定数据库操作顺序和过程实现。
执行计划通常以扁平的表格形式呈现,是一个事实树形结构。下面是一个基于SH模式中SALES、PRODUCTS表生成的查询:
SQL> select prod_category, avg(amount_sold)
2 from sales s, products p
3 where p.prod_id = s.prod_id
4 group by prod_category;
执行计划以表格的形式呈现:
——————————————
Id Operation Name
——————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
——————————————
通过事实树来解读执行计划:
GROUP BY
|
JOIN
_____|_______
| |
ACCESS ACCESS
(PRODUCTS) (SALES)
计划树的执行顺序是从下到上,上述的例子中,首先执行的是表的访问操作,也就是树的叶子部分。通过执行计划,我们可以看出访问操作是全表扫描,表扫描返回的数据行用来做连接操作,这里连接操作的类型是hash连接。最后对连接操作返回的数据行进行group-by,这里分组同样使用的是hash。
值得一提的是,查询优化器最终选择的执行计划是从众多的可选择执行计划中选取的代价最低的一个。这里的代价可以理解为性能的衡量指标,代价越低性能越好。查询优化器使用的代价模型是通过评估IO、CPU及网络等方面的数据计算出来的。
Oracle数据库中可以通过两种方式查看SQL语句的执行计划:
EXPLIAN PLAN 命令 — 这种方式没有实际执行SQL语句而仅仅把执行计划显示出来;
V$SQL_PALN — 从Oracle 9i开始引入了这个V$视图,通过该视图可以查询游标缓存中存在的SQL语句的执行计划。
在某些特定的场景下,使用EXPLAIN PLAN得到的执行计划可能和V$SQL_PLAN不同。比如,当SQL语句包含绑定变量时,EXPLAIN PLAN得到的执行计划会忽略绑定变量值,而V$SQL_PLAN中记录的执行计划则考虑了绑定变量值。
Oracle 9i引入了dbms_xplan包,随后的版本中该包的功能不断的增强,使得查看执行计划变得更加便利。这个包里提供了几个PL/SQL函数,用于从不同的数据源获取执行计划:
1. EXPLAIN PLAN command
2. V$SQL_PLAN
3. Automatic Workload Repository (AWR)
4. SQL Tuning Set (STS)
5. SQL Plan Baseline (SPM)
下面通过一些具体的例子展示如果使用dbms_xplan包提供的函数,产生和显示SQL语句的执行计划。
例1:使用EXPLAIN PLAN command和dbms_xplan.display函数
SQL> EXPLAIN PLAN FOR
2 select prod_category, avg(amount_sold)
3 from sales s, products p
4 where p.prod_id = s.prod_id
5 group by prod_category;Explained.
SQL> select plan_table_output
2 from table(dbms_xplan.display('plan_table', null, 'basic'));——————————————
Id Operation Name
——————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
——————————————
dbms_xplan.display使用的参数有:
plan table name (默认 'PLAN_TABLE')
statement_id (默认 null),
format (默认 'TYPICAL')
更详细的信息可以查看$ORACLE_HOME/rdbms/admin/dbmsxpln.sql。
例2:生成和显示会话中最后执行的SQL语句的执行计划
SQL> select prod_category, avg(amount_sold)
2 from sales s, products p
3 where p.prod_id = s.prod_id
4 group by prod_category;no rows selected
SQL> select plan_table_output
2 from table(dbms_xplan.display_cursor(null, null, 'basic'));——————————————
Id Operation Name
——————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
——————————————
dbms_xplan.display_cursor使用的参数有:
SQL ID (默认 null, null意味着当前会话中最后执行的SQL语句)
child number (默认 0),
format (默认 'TYPICAL')
例3:显示任何其他语句的执行计划
直接提供SQL_ID:
SQL> select plan_table_output from
2 table(dbms_xplan.display_cursor('fnrtqw9c233tt',null,'basic'));
间接查询获取SQL_ID:
SQL> select plan_table_output
2 from v$sql s,
3 table(dbms_xplan.display_cursor(s.sql_id,
4 s.child_number, 'basic')) t
5 where s.sql_text like 'select PROD_CATEGORY%';
例4:根据SQL Plan Baseline显示执行计划。SQL Plan Baseline是Oracle 11g中引入的新概念,用于支持SQL Plan Management (SPM)特性。
SQL> alter session set optimizer_capture_sql_plan_baselines=true;
Session altered.
SQL> select prod_category, avg(amount_sold)
2 from sales s, products p
3 where p.prod_id = s.prod_id
4 group by prod_category;no rows selected
假如上述语句执行超过一次,将会产生一个该语句的SQL Plan Baseline,可以通过下面的查询进行确认:
SQL> select SQL_HANDLE, PLAN_NAME, ACCEPTED
2 from dba_sql_plan_baselines
3 where sql_text like 'select prod_category%';SQL_HANDLE PLAN_NAME ACC
—————————— —————————— —
SYS_SQL_1899bb9331ed7772 SYS_SQL_PLAN_31ed7772f2c7a4c2 YES
上面创建的SQL Plan Baseline可以通过下面的方式显示:
直接提供SQL_HANDLE:
SQL> select t.* from
2 table(dbms_xplan.display_sql_plan_baseline('SYS_SQL_1899bb9331ed7772',
3 format => 'basic')) t;
间接查询获取SQL_HANDLE:
SQL> select t.*
2 from (select distinct sql_handle
3 from dba_sql_plan_baselines
4 where sql_text like 'select prod_category%') pb,
5 table(dbms_xplan.display_sql_plan_baseline(pb.sql_handle,
6 null,'basic')) t;
这两条语句的输出结果如下:
—————————————————————————-
SQL handle: SYS_SQL_1899bb9331ed7772
SQL text: select prod_category, avg(amount_sold) from sales s, products p
where p.prod_id = s.prod_id group by prod_category
—————————————————————————-
Plan name: SYS_SQL_PLAN_31ed7772f2c7a4c2
Enabled: YES Fixed: NO Accepted: YES Origin: AUTO-CAPTURE
—————————————————————————-Plan hash value: 4073170114
———————————————————
Id Operation Name
———————————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 VIEW index$_join$_002
4 HASH JOIN
5 INDEX FAST FULL SCAN PRODUCTS_PK
6 INDEX FAST FULL SCAN PRODUCTS_PROD_CAT_IX
7 PARTITION RANGE ALL
8 TABLE ACCESS FULL SALES
———————————————————
格式化
格式化参数可以进行高度定制,可以根据实际需要输出适当的信息。高级别的选项有:
Basic 输出的执行计划包括操作、选项和对象名(表、索引、物化视图等)
Typical 输出的执行计划包括Basic内容,加上和优化器相关的内部信息,如代价、大小、基数等,具体包括执行计划中的每个操作,优化器计算出来的每个操作的代价,每个操作返回的数据行数等,同时还会包括操作中所使用的评估谓词。Oracle CBO中存在两种谓词:ACCESS和FILTER。ACCESS表示根据查询条件,使用索引提取相关的数据块。FILTER表示数据块提取之后的评估。
All 输出的执行计划包括Typical内容,加上每个操作生成的表达式列表、提示别名、查询块的名称。
低级选项允许包括或者排除诸如谓词、代价之类的细节,举例如下:
SQL> select plan_table_output
2 from table(dbms_xplan.display('plan_table',null,'basic +predicate +cost'));——————————————————-
Id Operation Name Cost (%CPU)
——————————————————-
0 SELECT STATEMENT 17 (18)
1 HASH GROUP BY 17 (18)
* 2 HASH JOIN 15 (7)
3 TABLE ACCESS FULL PRODUCTS 9 (0)
4 PARTITION RANGE ALL 5 (0)
5 TABLE ACCESS FULL SALES 5 (0)
——————————————————-Predicate Information (identified by operation id):
—————————————————
2 – access("P"."PROD_ID"="S"."PROD_ID")select plan_table_output from
table(dbms_xplan.display('plan_table',null,'typical -cost -bytes'));—————————————————————————-
Id Operation Name Rows Time Pstart Pstop
—————————————————————————-
0 SELECT STATEMENT 4 00:00:01
1 HASH GROUP BY 4 00:00:01
* 2 HASH JOIN 960 00:00:01
3 TABLE ACCESS FULL PRODUCTS 766 00:00:01
4 PARTITION RANGE ALL 960 00:00:01 1 16
5 TABLE ACCESS FULL SALES 960 00:00:01 1 16
—————————————————————————-Predicate Information (identified by operation id):
—————————————————
2 – access("P"."PROD_ID"="S"."PROD_ID")
注解部分
dbms_xplan包生成执行计划时会将注释显示在NOTE部分,比如查询优化过程中使用了动态抽样或者星形转换应用在查询中。比如SALES表没有统计信息,优化器在分析代价时将会使用动态抽样,执行计划将会把这一过程记录在NOTE部分。
SQL> select plan_table_output
2 from table(dbms_xplan.display('plan_table',null,'basic +note'));——————————————
Id Operation Name
——————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
——————————————Note
—–
- dynamic sampling used for this statement
绑定窥视
在生成执行计划时,优化器会考虑绑定变量的实际值,这一过程就是所谓的绑定变量窥视。就像我们前面提到的那样,V$SQL_PLAN中记录的执行计划考虑了绑定变量值而EXPLAIN PLAN命令生成的执行计划并没有考虑这一点。从Oracle10gR2开始,dbms_xplan包可以显示用于生成特定计划或者游标的绑定变量值,只需要在display_cursor()函数时加上'+peeked_binds'参数即可。
下面的例子展示了一点:
SQL> variable pcat varchar2(50)
SQL> exec :pcat := 'Women'SQL> select PROD_CATEGORY, avg(amount_sold)
2 from sales s, products p
3 where p.PROD_ID = s.PROD_ID
4 and prod_category != :pcat
5 group by PROD_CATEGORY;SQL> select plan_table_output
2 from table(dbms_xplan.display_cursor(null,null,'basic +PEEKED_BINDS'));——————————————
Id Operation Name
——————————————
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
——————————————Peeked Binds (identified by position):
————————————–1 – CAT (VARCHAR2(30), CSID=2): 'Women'