Oracle优化05-执行计划

简介: Oracle优化05-执行计划

思维导图


20161229224353764.png


系列文章


Oracle-SQL Explain Plan解读


概述

如果要分析某条SQL的性能问题,通常来讲,我们首先要看SQL的执行计划,看看SQL的每一步执行计划是否存在问题。


如果某一条SQL平常执行的都很好,却有一天突然性能很差,如果排除了系统资源和阻塞的原因,那么基本上可以判断是执行计划出现了问题。


看懂执行计划变成了SQL优化(其实在大多数的情况下,SQL优化指的是SQL的性能问题定位)的先决条件。


在讨论SQL执行计划之前,我们需要知道执行计划当中一个非常重要的概念–Cardinality基数。


Cardinality基数


在我们看执行计划的每一步操作的时候,当前操作的Cardinality值表示CBO预期从一个行源(row source)返回的记录数。


一个行源可能是一个表、一个索引、也可能是一个子查询。

比如:

20161229225253067.png


当CBO无法准确的获取到Cardinality时,将会发生什么?


在执行计划中, card 就是Cardinality的缩写,它表示CBO估算当前操作预期获取的记录数。


Cardinality的值对于CBO做出正确的执行计划来说至关重要,如果CBO获得的Cardinality值不够准确(通常是没有做分析或者分析数据过旧导致),在执行成本计算上就会出现偏差,从而导致CBO错误的制定出执行计划。


下面演示下当CBO无法准确的获取到Cardinality时,将会发生什么?


创建一个数据分布非常不均匀的表T

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 
Connected as xxx@xgj
SQL> drop table t;
Table dropped
SQL> create table t as select 1 id , object_name from dba_objects;
Table created
SQL> update t set t.id=99 where rownum=1;
1 row updated
SQL> commit;
Commit complete
SQL> create index t_ind on t(id);
Index created
SQL> select id ,count(*) from t group by id ;
        ID   COUNT(*)
---------- ----------
         1      35251
        99          1


查看执行计划

##执行SQL
SQL> select /*+  dynamic_sampling(t 0)  */  * from t where id=1;
SQL> SELECT sql_id, child_number, a.* from v$sql a  where a.SQL_TEXT  like '%select /*+  dynamic_sampling(t 0)  */  * from t where id=1%';
SQL> select * from table(DBMS_XPLAN.display_cursor('bb22rwn4604yj',0));  


20161229233524663.png


说明:


/+ dynamic_sampling(t 0) / :目的是让CBO无法通过动态采样获取表中实际数据的情况,此时CBO只能根据数据字典中标T的非常有限的信息(比如表的extends数量,数据块的数量)来猜测表中的数据

从结果中可以看出,CBO猜测出id=1的数据为118条,而与实际上的数量 35251,相差甚远,所以CBO选择了索引而不是全表扫描

让我们看下实际的执行情况:

select  * from t where id=1;
SELECT sql_id, child_number, a.* from v$sql a  where a.SQL_TEXT  like '%select  * from t where id=1%';
select * from table(DBMS_XPLAN.display_cursor('3k9f6qpq9sbmn',0));  

20161229233944821.png


通过动态采样(10g及以上版本,如果表没有做过分析,ORACLE会自动通过动态采样的方式来收集分析数据),CBO估算出来的表中数量为39257 (Cardinality) 和实际的数量 35251非常接近,CBO判断ID=1的数据基本上等同于表中的数据,所以选择了全表扫描。


下面我们做一下表和索引分析

##先查看下
SQL>   select num_rows, avg_row_len, blocks, last_analyzed  from user_tables  where table_name = 'T';
  NUM_ROWS AVG_ROW_LEN     BLOCKS LAST_ANALYZED
---------- ----------- ---------- -------------
SQL> select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T';
    BLEVEL LEAF_BLOCKS DISTINCT_KEYS LAST_ANALYZED
---------- ----------- ------------- -------------
##对表和索引做分析
SQL>  exec   dbms_stats.gather_table_stats(user,'t',cascade => true);
PL/SQL procedure successfully completed
##重新查询信息 
SQL> select num_rows, avg_row_len, blocks, last_analyzed  from user_tables  where table_name = 'T';
  NUM_ROWS AVG_ROW_LEN     BLOCKS LAST_ANALYZED
---------- ----------- ---------- -------------
     35252          22        144 2016-12-30 0:
SQL>  select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T';
    BLEVEL LEAF_BLOCKS DISTINCT_KEYS LAST_ANALYZED
---------- ----------- ------------- -------------
         1          69             2 2016-12-30 0:
SQL> 

查看执行计划

SQL> select  *  from t a  where a.id=99;
SQL> SELECT sql_id, child_number, a.* from v$sql a  where a.SQL_TEXT  like '%select  *  from t a  where a.id=99%';
SQL> select * from table(DBMS_XPLAN.display_cursor('fa0r3kc8y5239',0));  


20161230001453088.png

通过对表的分析,CBO就可以获取到T表和索引的充足的信息。

上面的截图,CBO从分析数据中,获取到了id=99的数据6 rows , 所以选择了索引。


我们更新下数据

SQL>update t set id=99;
SQL>commit;


将id 全部更新为99 , 因为没有对表进行分析,所以CBO知道的信息还是旧的,重新查询 下 我们可以看到 CBO依然认为表T中的数据很少,依然选择的是索引。

20161230001453088.png

SQL> exec   dbms_stats.gather_table_stats(user,'t',cascade => true);
PL/SQL procedure successfully completed
## 如果使用 DBMS_XPLAN.display_cursor 查询执行计划的话,需要清空shared_pool ,或者换个SQL(目的是不匹配到shared_pool中的缓存)
SQL> alter system flush shared_pool;
System altered
##重新执行SQL
SQL> select  *  from t a  where a.id=99;
##获取SQL_ID等
SQL> SELECT sql_id, child_number, a.* from v$sql a  where a.SQL_TEXT  like '%select  *  from t a  where a.id=99%';
##查看执行计划
SQL> select * from table(DBMS_XPLAN.display_cursor('fa0r3kc8y5239',0)); 

20161230002436004.png

重新对表分析后,CBO获取了正确的Cardinality值。T表中id=99的数据3w+,所以全表扫描是最佳执行计划。



Cardinality是如何影响多表查询的?


在多表关联查询或者SQL中有子查询时,每个关联表或者子查询的Cardinality的值对主查询的影响非常大,甚至可以说,CBO就是依赖于各个关联表或者子查询Cardinality值来计算出最后的执行计划。


对于多表查询,CBO使用每个关联表返回的行数(Cardinality)决定使用设么样的方式来做表关联(比如Nested loops ,sm 或者 hash join),


对于子查询,它的Cardinality将决定子查询是使用索引还是使用全表扫描的方式访问数据。


举例说明:


数据如下:

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 
Connected as xxx@xgj
SQL> create table t1 (id  int , name varchar2(1000));
Table created
SQL> create table t2 (id  int , name varchar2(1000));
Table created
SQL> create index  ind_t1 on t1(id);
Index created
SQL> create index  ind_t2 on t2(id);
Index created
SQL> create index ind_t2_name on t2(name);
Index created
SQL> insert into t1 select object_id ,object_name from dba_objects ;
35258 rows inserted
SQL> commit;
Commit complete
SQL> insert into t2 values(1,'XGJ');
1 row inserted
SQL> commit;
Commit complete
##仅对t1的表和索引进行分析
SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade => true,method_opt => 'for all indexed columns');
PL/SQL procedure successfully completed
SQL> select * from t1 where id in (select /*+  dynamic_sampling(t2 0)  cardinality(t2 10000) */   id  from t2  where name = 'XGJ');
  ID            NAME
------------- ----------------------
SQL>select * from v$sql a where a.SQL_TEXT 
like '% /*+  dynamic_sampling(t2 0)  cardinality(t2 10000) */  %';
##DBMS_XPLAN.display_cursor中查看执行计划
SQL>select * from table(dbms_xplan.display_cursor('2c7hx20a3bhxx',0));

20170104001431531.png


解析:

我们发出的子查询的SQL,同时使用了hint

/*+  dynamic_sampling(t2 0)  cardinality(t2 10000) */


  • dynamic_sampling(t2 0):禁止动态采用
  • cardinality(t2 10000):告诉CBO从T2表中取10000条记录

通过这种方式,我们模拟子查询中返回的结果数,同时为了让CBO完全依赖这个信息生成执行计划,我们禁止了子查询使用动态采样(dynamic_sampleing 设置为0)。


可以看到,当CBO得到来自于子查询的结果集(Rows )的记录为10000条时,采用了hash join semi的执行计划,hash join通常适用于两张关联的表都比较大的时候


如果我们把子查询的结果集变得很小会怎样呢?

来看我们下面的栗子:

SQL> select * from t1 where id in (select /*+  dynamic_sampling(t2 0)  cardinality(t2  1) */   id  from t2  where name = 'XGJ');
  ID       NAME
------ ----------


同样的 我们来看下执行计划:


20170104001813877.png


我们在查询中将子查询的返回值设置为1,即

Cardinality(t2,1)

此时CBO选择了两个表通过nested loop join 进行关联的执行计划,因为子查询只有1条记录,这个时候CBO会选择最适合择偶张情况的netsted loops join关联方式。


从这个试验中我们可以得到如下结论:


子查询的Cardinality的值,直接影响了主查询的执行计划,如果CBO对子查询的Cardinality判断有误,那么饿主查询的执行计划很有可能是错误的。


再看量表关联的查询情况,还是用上面的栗子

##上面执行后 user_index有值了...清掉先
select num_rows, avg_row_len, blocks, last_analyzed  from user_tables  where table_name = 'T2';
select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T2';
SQL> analyze table t2 delete statistic
  2  ;
Table analyzed
SQL> alter system flush shared_pool;
System altered
SQL> select /*+  dynamic_sampling(t2 0)  cardinality(t2 10000) */  * from t1,t2 where t1.id=t2.id;
....省略输出


查看执行计划


20170104002253051.png

这个栗子中,CBO认为T2关联的数据足够多,而且T1又足够大,所以在这种情况下,hash join 是最合适的。


SQL>select /*+  dynamic_sampling(t2 0)  cardinality(t2  1) */  * from t1,t2 where t1.id=t2.id;
....省略输出

查看执行计划:


20170104002424192.png


这时候因为T2给CBO提供的信息,只有1条记录做关联查询,所以CBO选择了nested loop join .


总结


以上的例子主要说明Cardinality对CBO生成执行计划的影响,所以我们在看多表查询的时候,一定要注意每个操作返回的Cardinality的值,如果这个值明显的不对,那么很有可能操作的表的分析数据出了问题,或者没有分析。


比如在上面的栗子中,我们确切的知道T2表的数据很小,而在执行计划中却显示10000条,这显然不对了,这个时候叫就要检查问题所在,看看T2表是不是曾经有很多数据,删除之后没有做重新分析等等。


SQL的执行计划


如果一条SQL的性能出现了问题,首先应该看一下它的执行计划,以便确定(或者猜测)问题的所在。


生成SQL的执行计划时Oracle在对SQL做硬分析时的一个非常重要的步骤,它制定出一个方案告诉Oracle在执行这条SQL时以什么样的方式访问数据: 索引扫描? 全表扫描? , 是hash join 还是 netsted loops join 等。


如何得到一个SQL的执行计划


  • explain plan for
  • SQLPLUS命令 set autotrace on
  • 第三方软件提供的GUI工具,比如Toad ,PL/SQL Developer


具体看: Oracle-SQL Explain Plan解读


如何看懂一个SQL的执行计划


首先得到一个SQL的执行计划

我们使用select * from table(DBMS_XPLAN.display_cursor(.......))的方式获取select * from t1 ,t2 where t1.id = t2.id如下的执行计划:

20170104235611284.png


其中,rows列 就是我们上面说到的 Card(Cardinality) 9i以前的版本使用的是Card.


如何阅读呢?

我们首先从说缩进度最大的行读取,它是最先被执行的步骤

ID=3和ID=4是最先被执行的20170105001048163.png当两行缩进一样时,最上面的最先被执行,在这里就是 ID=3的先执行,然后是ID=4的。

然后是缩进次之的,

这里就是


20170105001321900.png


紧接着锁进再次之的,以此类推

在这里就是是ID=1的 ,最后是ID=0的。

我们也可在PL/SQL中F5查看执行计划后,一步步的跟踪


20170105001717014.gif


这就是这个SQL的执行过程。


大致意思是:


从T2表读取第一行数据 是否符合条件


如果符合就拿出一行来,然后到索引IND_T1 中找到对应的值,然后重复,直到把整个T2表全表扫描完,这个过程就叫NESTED LOOPS .


当T2表被扫描完之后,会产生一个结果集,这个结果集是 IND_T1的一个索引集,然后ORACLE根据说印键值上的rowid 去T1表找对应的记录,就是这一步: Operation TABLE ACCESS BY INDEX ROWID


然后还有个NESTED LOOPS .(疑惑待思考)


最后将结果返回: Operation SELECT STATEMENT


执行计划中的值说明


  • ID列: 是一个序号,注意,它的大小并不是执行的先后顺序。
  • Operation列: 是当前的操作内容。
  • Rows 列: 就是当前操作的cardinality,Oracle估算当前操作的返回结果集
  • Cost(cpu): Oracle计算出来的一个数值(代价),用于说明SQL执行的代价
  • Time列: Oracle估算当前操作的时间。


还有些重要信息。比如

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("T1"."ID"="T2"."ID")


这一段是来说明谓词信息和数据获取的方式,它的意思在ID=4的那一列,通过访问索引寻找数据,而不是访问原表数据。


还有个常见的方式 filter ,我们这里来解释下access和filter的区别


如果执行计划显示access, 就表示这个谓词条件的值将会影响数据的访问路径(全表还是索引,这里是索引)


如果执行计划显示filter,表示谓词条件的值并不会影响数据的访问路径,只起到过滤的作用。


执行计划的最后一步是

20170105215124382.png

这一步提示用户CBO当前使用的技术,需要用户咋分析执行计划时考虑到这些因素,比如现在提示这些信息时,当前表使用了动态采样,通过这个提示,我们就知道这个表可能没有做过分析。

举例:

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 
Connected as xxx@xgj
SQL> create table t(x int);
Table created
SQL>  begin
   for i in 1 .. 1000 loop
     insert into t values (i);
   end loop;
   commit;
 end;
 /
SQL> select a.SQL_ID ,a.CHILD_NUMBER from v$sql a where a.SQL_TEXT like 'select * from t';
SQL_ID CHILD_NUMBER
------------- ------------
89km4qj1thh13            0
SQL> select * from table(dbms_xplan.display_cursor('89km4qj1thh13',0));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  89km4qj1thh13, child number 0
-------------------------------------
select * from t
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS FULL| T    |     1 |    13 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------
Note
-----
   - dynamic sampling used for this statement (level=2)
17 rows selected
SQL> 


我们手工执行进行表分析后,重新查看下

##表分析
SQL> exec dbms_stats.gather_table_stats(user,'t');
PL/SQL procedure successfully completed
##因为是从shared_pool中加载数据,如果不清空,会影响执行计划
SQL> alter system flush shared_pool;
System altered
SQL>select * from t;
....
SQL>select a.SQL_ID ,a.CHILD_NUMBER,a.SQL_TEXT from v$sql a where a.SQL_TEXT like 'select * from t%';
....
SQL> select * from table(dbms_xplan.display_cursor('ckzaraqcmkr2f',0));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  ckzaraqcmkr2f, child number 0
-------------------------------------
select * from t
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |     3 (100)|          |
|   1 |  TABLE ACCESS FULL| T    |  1000 |  4000 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------
13 rows selected
SQL> 


执行计划中已经没有动态采样的提示信息了。


第一次执行SQL,CBO发现表没有做表分析,于是使用动态采样的方式获取数据信息。


SQL第二次执行时,CBO发现表已经分析过了,于是就不会再使用动态分析,而是直接使用分析数据。


这里会出现两种情况:


如果没做表分析,CBO可以通过动态采样的方式来分析数据,也可以获取到正确的执行计划

如果分析过,但是分析信息过旧,这时候CBO不会再使用动态采样,而是使用这些旧的分析数据,有可能导致错误的执行信息


总结


以上我们阐述了执行计划输出的全部内容。 当我们在看执行计划时,不能只看执行计划的本身,还要看下面的谓词和提示信息,这都非常有帮助。

相关文章
|
2天前
|
SQL Oracle 关系型数据库
Oracle数据库优化方法
【10月更文挑战第25天】Oracle数据库优化方法
14 7
|
2天前
|
存储 Oracle 关系型数据库
Oracle数据库优化策略
【10月更文挑战第25天】Oracle数据库优化策略
10 5
|
24天前
|
SQL Oracle 关系型数据库
Oracle SQL:了解执行计划和性能调优
Oracle SQL:了解执行计划和性能调优
38 1
|
3月前
|
监控 Oracle 关系型数据库
"深度剖析:Oracle SGA大小调整策略——从组件解析到动态优化,打造高效数据库性能"
【8月更文挑战第9天】在Oracle数据库性能优化中,系统全局区(SGA)的大小调整至关重要。SGA作为一组共享内存区域,直接影响数据库处理能力和响应速度。本文通过问答形式介绍SGA调整策略:包括SGA的组成(如数据缓冲区、共享池等),如何根据负载与物理内存确定初始大小,手动调整SGA的方法(如使用`ALTER SYSTEM`命令),以及利用自动内存管理(AMM)特性实现智能调整。调整过程中需注意监控与测试,确保稳定性和性能。
251 2
|
5月前
|
SQL Oracle 关系型数据库
Oracle游标的使用和优化技巧
Oracle游标的使用和优化技巧
|
4月前
|
SQL Oracle 关系型数据库
Oracle游标的使用和优化技巧
Oracle游标的使用和优化技巧
|
4月前
|
SQL 存储 Oracle
Oracle数据库中游标的工作原理与优化方法
Oracle数据库中游标的工作原理与优化方法
|
6月前
|
监控 Oracle 关系型数据库
Oracle 12c的Adaptive执行计划:数据的“聪明导航员”
【4月更文挑战第19天】Oracle 12c的Adaptive执行计划是数据库查询的智能优化工具,能根据实际运行情况动态调整执行策略。它像一个聪明的导航系统,不仅生成初始执行计划,还能实时监控并适应统计信息和资源变化,例如自动切换索引或调整并行度。此外,它支持自适应连接和统计信息收集,提升处理复杂查询和变化数据环境的能力。数据管理员应充分利用这一特性来优化查询性能和用户体验。
|
6月前
|
存储 Oracle 数据管理
Oracle 12c的自动数据优化(ADO)与热图:数据管理的“瘦身”与“透视”艺术
【4月更文挑战第19天】Oracle 12c的ADO和热图技术革新数据管理。ADO智能清理无用数据,优化存储,提升查询速度,实现数据"瘦身";热图则以直观的视觉表示展示数据分布和状态,助力识别性能瓶颈,犹如数据的"透视"工具。这两项技术结合,强化数据管理,为企业业务发展保驾护航。
|
12月前
|
存储 SQL Oracle
Oracle优化避免索引失效
Oracle优化避免索引失效
356 0

推荐镜像

更多