Python大数据与SQL优化笔 QQ群:771686295
查看SQL执行计划是SQL优化的第一步,所以我们要能正确掌握查看执行计划的方法。
用AUTOTRACE查看执行计划
SQL> set autot
Usage: SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]]
set auto on :运行SQL且显示执行SQL运行结果,执行计划,统计信息
set auto trace :运行SQL且不显示执行SQL运行结果,显示执行计划,统计信息
set auto trace exp :不运行查询SQL,但是运行DML SQL,且不显示执行SQL运行结果,统计信息,显示执行计划
set auto trace stat :运行SQL且只显示统计信息
set auto off :关闭AUTOTRACE
SQL> set autot on
SQL> select count(*) from emp;
COUNT(*)
----------
15
Execution Plan
----------------------------------------------------------
Plan hash value: 2937609675
-------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FULL SCAN| PK_EMP | 14 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1 consistent gets
0 physical reads
0 redo size
526 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
########################################################
SQL>set autot trace
SQL> select count(*) from emp;
Execution Plan
----------------------------------------------------------
Plan hash value: 2937609675
-------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FULL SCAN| PK_EMP | 14 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1 consistent gets
0 physical reads
0 redo size
526 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
############################################
SQL> set autot trace exp
SQL> select count(*) from emp;
Execution Plan
----------------------------------------------------------
Plan hash value: 2937609675
-------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FULL SCAN| PK_EMP | 14 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------
##########################################
SQL> set autot trace stat
SQL> select count(*) from emp;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1 consistent gets
0 physical reads
0 redo size
526 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> set autot off
2. 使用EXPLAIN PLAN FOR查看执行计划(推荐,因为可以显示谓语信息)
SQL> explain plan for
select * from emp; 2
Explained.
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 532 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| EMP | 14 | 532 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
8 rows selected.
###########################################
#查看高级执行计划
SQL> explain plan for
select * from emp; 2
Explained.
SQL> select * from table(dbms_xplan.display(null,null,'advanced -projection'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 532 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| EMP | 14 | 532 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 - SEL$1 / EMP@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
FULL(@"SEL$1" "EMP"@"SEL$1")
OUTLINE_LEAF(@"SEL$1")
ALL_ROWS
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DB_VERSION('11.2.0.1')
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
IGNORE_OPTIM_EMBEDDED_HINTS
END_OUTLINE_DATA
*/
27 rows selected.
####################################################
#查看内存中缓存的SQL的真实执行计划,关键在于找到SQL_ID
SQL> select * from table(dbms_xplan.display_cursor(sql_id =>'c7gvq5j6k07aq',cursor_child_no=>0,format =>'ALL ALLSTATS LAST NOTE ADVANCED -projection'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID c7gvq5j6k07aq, child number 0
-------------------------------------
select b.USERNAME,b.sid,b.SERIAL#,a.sql_fulltext,b.status,a.object_stat
us,b.event,b.STATE,b.WAIT_TIME,b.SECONDS_IN_WAIT,b.TIME_REMAINING_MICRO,
b.WAIT_TIME_MICRO,a.sql_id,b.PROGRAM from v$sql a,v$session b where
b.sql_address=a.address
Plan hash value: 3784909302
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)| OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1 (100)| | | |
| 1 | NESTED LOOPS | | 1 | 2307 | 0 (0)| | | |
| 2 | NESTED LOOPS | | 1 | 2260 | 0 (0)| | | |
| 3 | MERGE JOIN CARTESIAN | | 100 | 208K| 0 (0)| | | |
|* 4 | FIXED TABLE FULL | X$KGLCURSOR_CHILD | 1 | 2042 | 0 (0)| | | |
| 5 | BUFFER SORT | | 100 | 9100 | 0 (0)| 2048 | 2048 | 2048 (0)|
| 6 | FIXED TABLE FULL | X$KSLWT | 100 | 9100 | 0 (0)| | | |
|* 7 | FIXED TABLE FIXED INDEX| X$KSUSE (ind:1) | 1 | 127 | 0 (0)| | | |
|* 8 | FIXED TABLE FIXED INDEX | X$KSLED (ind:2) | 1 | 47 | 0 (0)| | | |
----------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$66C44981
4 - SEL$66C44981 / X$KGLCURSOR_CHILD@SEL$4
6 - SEL$66C44981 / W@SEL$7
7 - SEL$66C44981 / S@SEL$7
8 - SEL$66C44981 / E@SEL$7
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$66C44981")
MERGE(@"SEL$1B7D9AE9")
MERGE(@"SEL$641071AC")
OUTLINE(@"SEL$1")
OUTLINE(@"SEL$1B7D9AE9")
MERGE(@"SEL$68B588A0")
OUTLINE(@"SEL$641071AC")
MERGE(@"SEL$07BDC5B4")
OUTLINE(@"SEL$5")
OUTLINE(@"SEL$68B588A0")
MERGE(@"SEL$7")
OUTLINE(@"SEL$2")
OUTLINE(@"SEL$07BDC5B4")
MERGE(@"SEL$4")
OUTLINE(@"SEL$6")
OUTLINE(@"SEL$7")
OUTLINE(@"SEL$3")
OUTLINE(@"SEL$4")
FULL(@"SEL$66C44981" "X$KGLCURSOR_CHILD"@"SEL$4")
FULL(@"SEL$66C44981" "W"@"SEL$7")
FULL(@"SEL$66C44981" "S"@"SEL$7")
FULL(@"SEL$66C44981" "E"@"SEL$7")
LEADING(@"SEL$66C44981" "X$KGLCURSOR_CHILD"@"SEL$4" "W"@"SEL$7" "S"@"SEL$7" "E"@"SEL$7")
USE_MERGE_CARTESIAN(@"SEL$66C44981" "W"@"SEL$7")
USE_NL(@"SEL$66C44981" "S"@"SEL$7")
USE_NL(@"SEL$66C44981" "E"@"SEL$7")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("INST_ID"=USERENV('INSTANCE'))
7 - filter(("S"."INST_ID"=USERENV('INSTANCE') AND BITAND("S"."KSSPAFLG",1)<>0 AND
BITAND("S"."KSUSEFLG",1)<>0 AND "S"."KSUSESQL"="KGLHDPAR" AND "S"."INDX"="W"."KSLWTSID"))
8 - filter("W"."KSLWTEVT"="E"."INDX")
Note
-----
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
84 rows selected.
#######################################################
#查看AWR报告中的SQL执行计划
select * from table(dbms_xplan.display_cursor('&sql_id',0));
select * from table(dbms_xplan.display_cursor('&sql_id',1));
3.查看A-Time真实执行计划
关键在于/*+ gather_plan_statistics*/ hint
SQL> select /*+ gather_plan_statistics*/ count(*) from emp;
COUNT(*)
----------
15
SQL> SELECT * FROM TABLE(dbms_xplan.display_cursor(NULL,NULL,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID cn2q98tcvnkd6, child number 0
-------------------------------------
select /*+ gather_plan_statistics*/ count(*) from emp
Plan hash value: 2937609675
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 1 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 1 |
| 2 | INDEX FULL SCAN| PK_EMP | 1 | 14 | 15 |00:00:00.01 | 1 |
-------------------------------------------------------------------------------------
14 rows selected.
Starts: 这个操作执行次数
E-Rows:优化器评估的行数
A-Rows:真实的操作行数
A-Time:真实的操作累计时间
Buffers:累计的逻辑读
Reads:累计的物理读
获取执行计划的方法大概就上面这些,下面说说我个人看执行计划时候的会注意的地方
recursive calls 表示递归调用,SQL第一次发生硬解析,会隐含地调用一些内部SQL,所以第一次执行SQL的是这个参数可能会大于1,但是当第二次执行SQL时候就不需要调用了,这个参数就会变成0, 如果这个参数一直很大,就要看看SQL里面是不是调用了一些自定义函数导致的,这样可能会导致性能问题。
consistent gets 表示逻辑读,SQL优化的关键就是减少逻辑读的大小
如果一个SQL的consistent gets 大于SQL中所有表的总段大小,则说明这个SQL的优化空间比较大。一般而言,每获取一行开销5个以下的逻辑读是属于可以接受的范围的,为什么不关注物理读,因为每次执行的时候,物理读会随着BUFFER CACHE的命中而变化的,而逻辑读却是保持不变的。
构造脚本:
drop table t;
create table test as select * from dba_objects t;
insert into t select * from test;
insert into t select * from test;
insert into t select * from test;
insert into t select * from test;
insert into t select * from test;
update t set object_id=rownum;
commit;
SQL> analyze table t compute statistics for table for all indexes for all indexed columns;
表已分析。
SQL> select count(*) from t;
COUNT(*)
----------
1680640
--------------------------------------------------------------------------------
进行autotrace 分析:
SQL> set autotrace traceonly
SQL> select * from t where object_id<101;
已选择100行。
已用时间: 00: 00: 04.22
执行计划
----------------------------------------------------------
Plan hash value: 1357081020
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 9700 | 5134 (3)| 00:01:02 |
|* 1 | TABLE ACCESS FULL| T | 100 | 9700 | 5134 (3)| 00:01:02 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OBJECT_ID"<101)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
22990 consistent gets
0 physical reads
0 redo size
5317 bytes sent via SQL*Net to client
465 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100 rows processed
本查询最终返回100行,完成时间是4秒,产生了 22990个逻辑读,根据22990/100=229的比例计算,平均每获取一行开销了229个逻辑读,远不足获取单行记录小于5个逻辑读的标准,由此我们可以判断,此SQL相当低效,经分析是缺少相应的索引。
在建了索引后,产生的效果如下,返回100行产生了18个逻辑读,根据18/100=0.18的比例计算,平均每获取一行开销了0.18个逻辑读,符合获取单行记录小于5个逻辑读的标准,效率也有了极大提升,完成时间仅0.1秒
SQL> alter table t add constraint PK_t primary key(object_id);
表已更改。
SQL> analyze table t compute statistics for table for all indexes for all indexed columns;
表已分析。
SQL> select * from t where object_id<101;
已选择100行。
已用时间: 00: 00: 00.01
执行计划
----------------------------------------------------------
Plan hash value: 115135762
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 9700 | 5 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 100 | 9700 | 5 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | PK_TEST | 100 | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_ID"<101)
统计信息
----------------------------------------------------------
1 recursive calls
0 db block gets
18 consistent gets
0 physical reads
0 redo size
5317 bytes sent via SQL*Net to client
465 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100 rows processed
4.rows processed 表示SQL一共返回的行数,这个参数也是很重要的,根据这个参数我们可以大概知道SQL走HASH JOIN合适还是走NEST LOOP合适,一般rows processed比较大一般走HASH JOIN比较好,但是rows processed比较小走NEST LOOP比较好。
5.优化器的执行计划很多时候都是错误的,这时候可能会导致性能问题,比如驱动表本来1000W行,结果被评估成10行,这样大概率走NEST LOOP,这样就会出现问题,这时候我们就可以用select /*+ gather_plan_statistics*/ count(*) from emp; 这种方式去查看SQL的真实执行计划,去看看SQL的真实执行时间,每行返回的真实返回数据,真实的执行次数。
比如
Starts: 这个操作执行次数 #驱动表行数评估行数出错,被驱动表这个次数会比较大,可能会出现问题。
E-Rows与A-Rows 差距比较大的时候,也可能会出现问题
出现问题之后的解决办法大概如下:
收集表的统计信息,直方图信息
使用hint走我们想要的执行计划
修改SQL
今天就写到这里。
个人意见,望指正。