首先创建测试的表:
SQL> show user
USER 为 "U1"
SQL> create table t1 (id int,name varchar2(1000));
表已创建。
SQL> create table t2 (id int,name varchar2(1000));
表已创建。
SQL> create index ind_t1 on t1(id);
索引已创建。
SQL> create index ind_t2 on t2(id);
索引已创建。
SQL> create index ind_t2_name on t2(name);
索引已创建。
SQL> conn /as sysdba
已连接。
SQL> insert into u1.t1 select object_id,object_name from dba_objects;
已创建50607行。
SQL> commit;
提交完成。
下面看这样一个执行计划:
SQL> set autotrace traceonly
SQL> select t1.* from t1,t2 where t1.id = t2.id and t1.id = 5 and t2.name = 'A';
未选定行
执行计划
----------------------------------------------------------
Plan hash value: 1946640164
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1030 | 4 (0)| 00:00:01 |
| 1 | MERGE JOIN CARTESIAN | | 1 | 1030 | 4 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | T2 | 1 | 515 | 2 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 1 | 515 | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 515 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IND_T1 | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("T2"."NAME"='A' AND "T2"."ID"=5)
5 - access("T1"."ID"=5)
Note
-----
- dynamic sampling used for this statement
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
326 bytes sent via SQL*Net to client
389 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed
看执行计划时,我们首先从缩进度最大的行读起,它是最先被执行的步骤。在这个执行计划中ID=5的最先被执行,既是:
|* 5 | INDEX RANGE SCAN | IND_T1 | 1 | | 1 (0)| 00:00:01 |
当两行的缩进一样时,最上面的最先被执行,在这里就是ID=2:
|* 2 | TABLE ACCESS FULL | T2 | 1 | 515 | 2 (0)| 00:00:01 |
然后是ID=3:
| 3 | BUFFER SORT | | 1 | 515 | 2 (0)| 00:00:01 |
根据上面的规则,整个的执行顺序就是:5 --> 4 --> 2 --> 3 --> 1 --> 0。这就是整个sql的执行过程。
Id:是一个序号,注意,他的大小并不是执行的先后顺序。
Operation:是当前操作的内容。
Name:当前操作的对象。
Rows:就是当前操作的cardinality,oracle估算当前操作的返回结果集。
Cost:oracle计算出来的一个数值(代价),用于说明sql执行的代价。
Time :oracle估算当前操作的时间。
执行计划中还有一些比较有用的信息:
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("T2"."NAME"='A' AND "T2"."ID"=5)
5 - access("T1"."ID"=5)
这一段用来说明谓词信息和数据获取的方式。意思就是在第一步使用了如下过滤条件:
5 - access("T1"."ID"=5)
第三步使用了如下过滤条件:
2 - filter("T2"."NAME"='A' AND "T2"."ID"=5)
这里要注意access和filter的区别:
access:表示这个谓词条件的值将会影响数据的访问路劲(表还是索引,在这里索引 IND_T1)。
filter:表示谓词条件的值并不会影响数据访问的路径,只起到过滤的作用。
在我们看执行计划的时候,如果谓词是access的,就要思考对于谓词的条件,使用的访问路径是否正确。执行计划下面是:
Note
-----
- dynamic sampling used for this statement
这一步提示当前表使用了动态采样,通过这个提示,我们就知道,这个表可能没有做过分析。我们把整个sql的执行过程翻译成语言描述大概如下:
这个执行计划采用了动态采样收集统计信息。
ID=5:首先根据索引 IND_T1 找到符合过滤条件("T1"."ID"=5)的记录,这里oracle估算到大概有1行记录。
ID=4:然后oracle根据rowid找到t1表中的这条记录。
ID=2:然后全表扫描t2,使用的过滤条件是("T2"."NAME"='A' AND "T2"."ID"=5)。
ID=3:然后在内存中排序。
ID=1:然后t1表和t2表做关联。
ID=0:然后将结果返回。
在最后面还有使用oracle资源的统计信息:
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
326 bytes sent via SQL*Net to client
389 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed
0 recursive calls:在执行这个sql语句中oracle内部额外执行的sql语句的个数。
0 db block gets:从buffer cache中读取的数据块的数量(通过update/delete/select for update读的次数)。
3 consistent gets:从buffer cache中读取的undo数据块的数量(通过不带for update的select 读的次数)。
0 physical reads:从硬盘读取的数据块的数量。
0 redo size:执行sql过程中产生redo的大小。
326 bytes sent via SQL*Net to client:通过SQL*Net发送给客户端的字节数。
389 bytes received via SQL*Net from client:通过SQL*Net接受客户端的字节数。
1 SQL*Net roundtrips to/from client:网络往返次数。(在sql*plus 中可以受arraysize参数影响。)
0 sorts (memory):内存排序记录的数量。
0 sorts (disk):磁盘排序记录的数量。
0 rows processed:实际返回记录的数量。