SQL优化-同SQL不同执行计划(一)

时间:2022-12-19 07:41:47

 问题来源:CSDN Oracle技术论坛

问题         myepoch 提出相同 SQL 因为检索的值,不同执行效率差别巨大。
问题环境: Oracle 10g TBNC_P 表在 adminaccount B 树索引,
TBNC_P 总记录数: 33422
TBNC_A 总记录数: 40782
结果       已经解决,优化后的 SQL 执行时间,均小于 0.032
SQL 1 】:
select art.article_id, art.article_title, aps.adminaccount
 from TBNC_A art, TBNC_P aps
 where art.column_id = aps.scopestr
   and aps.funcnodepath = 'A001B002C002D002E003'
   and aps.adminaccount = ' lgm ';
 
SQL 2 】:
select art.article_id, art.article_title, aps.adminaccount
 from TBNC_A art, TBNC_P aps
 where art.column_id = aps.scopestr
   and aps.funcnodepath = 'A001B002C002D002E003'
   and aps.adminaccount = ' admin_two ';
 
两个 SQL 除了【 aps.adminaccount 】等于的值不同,其他完全一致,
            执行时间 ( 平均 )      执行结果数
SQL 1        3.906 S                                   148
SQL 2        0.531 S                                   11421
 
执行计划如下:
SQL1
3 -----------------------------------------------------------------------------------------------------  
4  ¦ Id   ¦ Operation                     ¦ Name          ¦ Rows   ¦ Bytes  ¦ Cost (%CPU) ¦ Time      ¦  
5 -----------------------------------------------------------------------------------------------------  
6  ¦   0  ¦ SELECT STATEMENT              ¦               ¦   888  ¦ 65712  ¦  4291   (1) ¦ 00:00:52  ¦  
7  ¦*  1  ¦  HASH JOIN                    ¦               ¦   888  ¦ 65712  ¦  4291   (1) ¦ 00:00:52  ¦  
8  ¦*  2  ¦   TABLE ACCESS BY INDEX ROWID ¦ POWERSCOPE    ¦    11  ¦   330  ¦     3   (0) ¦ 00:00:01  ¦  
9  ¦*  3  ¦    INDEX RANGE SCAN           ¦ SYS_LGM       ¦    87  ¦        ¦     1   (0) ¦ 00:00:01  ¦  
10  ¦   4  ¦   TABLE ACCESS FULL          ¦ CMSARTICLE    ¦ 40782  ¦  1752K ¦  4288   (1) ¦ 00:00:52  ¦  
11 -----------------------------------------------------------------------------------------------------  
SQL2
3 -------------------------------------------------------------------------------------------  
4  ¦ Id   ¦ Operation           ¦ Name                  ¦ Rows   ¦ Bytes  ¦ Cost (%CPU) ¦ Time      ¦  
5 -------------------------------------------------------------------------------------------  
6  ¦   0  ¦ SELECT STATEMENT    ¦                       ¦ 44229  ¦  3196K ¦  4344   (1) ¦ 00:00:53  ¦  
7  ¦*  1  ¦  HASH JOIN          ¦                       ¦ 44229  ¦  3196K ¦  4344   (1) ¦ 00:00:53  ¦  
8  ¦*  2  ¦   TABLE ACCESS FULL ¦ POWERSCOPE            ¦   541  ¦ 16230  ¦    56   (2) ¦ 00:00:01  ¦  
9  ¦   3  ¦   TABLE ACCESS FULL ¦ CMSARTICLE            ¦ 40782  ¦  1752K ¦  4288   (1) ¦ 00:00:52  ¦  
10 -------------------------------------------------------------------------------------------  
 
【疑惑四】 【执行计划成本】等价于【执行时间】
【疑惑三】 SQL1 检索的数据比 SQL2 的少,为什还比 SQL2 慢?
【疑惑二】 SQL1 中使用了索引【 SYS_LGM 】反而还比 SQL2 【全表扫描】慢?
【疑惑一】 相同 SQL 语句,为什么存在【执行计划】的差别?
 
SQL 优化的方面没有什么定式,带着上述问题,开始在没有测试环境的前提下,开始和 myepoch 一起排查测试,寻找问题。
 
【疑惑一解答】
大多数人都会一样这样观点,相同 SQL 执行计划应该是相同的,这样的观点是错误,上面的问题就是很好的佐证,数据分布和存储特点都会影响执行计划的选择, Oracle 选择的执行计划在统计信息正确的前提下, 99% 都不会有问题。
 
我做一个不算恰当的假设  
假设我们有一个表,叫 TableA ,它有三个字段分别是 ColA ColB ColC ,表没有主键,所有字段都非空。每条记录存储在一个数据块中。
ColB 值只能是 ('sys','user','costomer') 这三种的其中一种,数据表中有一万条记录。  
ColB 数据的分布分别是 (2%,95%,3%) 
再假设,我们在 ColB 上有一个 B 树索引。  
如果我们写这样的 Sql: 
SELECT * 
 FROM TableA 
 where ColB = 'user' 
你说上面的 SQL 一定会使用在 ColB 上的索引吗?   结果是【不一定】
Oracle 的执行计划会是根据统计分析做出的,执行计划会随着数据的变化而变化。  
以上 SQL 多数时候是不使用索引的,因为 Select 字段的 * 影响了执行计划,  
假设 Oralce 通过索引进行快速扫描,假设它会执行 9500 次检索,但为了取得 ColA ColC 数据 Oracle 不得不在通过 Rowid 获取数据,获取数据需要 9500 次,这样 Oracle 就需要 19000 次操作才能完成工作。
但要使用全表扫描只需要 10000 次就可以搞定了,所以这时候 Oracle 会选择全表扫描。不会选择使用索引。
当让 Oralce 不会执行 10000 次,它会通过好多种方法和手段去尽快完成工作,上面的说明只是方便说明而已,
但是基本原理相当。
 
如果我们改变 SQL 语句:
SELECT ColB 
 FROM TableA 
 where ColB = 'user'
你说 Oracle 还会选择【索引快速扫描】和【全表扫描】那种执行计划呢?
结果肯定是【索引快速扫描】。
 
如果我们再改变一下 SQL 语句:
SELECT * 
 FROM TableA 
 where ColB = 'sys'
你说 Oracle 优化器会选择【索引快速扫描】和【全表扫描】那种执行计划呢?
结果是【索引快速扫描】, 不是 【全表扫描】。
原因很简单,如果先通过【索引快速扫描】寻找到 2% 的数据的 Rowid ,然后在通过 Rowid 找到 ColA ColB 的记录,
明显比【全表扫描】快, Oralce 的代价不过 400 而已。
 
所以可以看出,Where不光是影响索引使用的唯一条件Select也会影响索引使用。千万比忘记了这一点,很重要。
绝大多数时候Oracle自己可以自动收集【统计信息】,所以保证数据【统计信息】非常重要。
【统计信息】会影响【执行计划】。
综合上述,需要确认【统计信息】是否正确,确认语句如下:
select user_tables.num_rows,user_tables.last_analyzed,user_tables.*
 from user_tables
where user_tables.table_name
in ('TBNC_A','TBNC_P');
 
更具上述分析,所以请求 myepoch 提供了【统计信息】,并且手动启动了【统计信息】包。
执行语句如下:
dbms_stats.gather_table_stats(ownname => '',tabname => 'TBNC_A');
【统计信息】包执行前,如下 ( 摘要 )
表名                                          记录数   最后统计时间    数据块数
TBNC_P          33422 2008-5-13        244
TBNC_A                        40782 2008-2-15        19535
 
【统计信息】包执行后,如下 ( 摘要 )
表名                                          记录数   最后统计时间    数据块数
TBNC_P          33422 2008-5-13        244
TBNC_A                        40782 2008-5-14        19535
 
统计后【SQL1】和【SQL2】未发生任何变化,判读错误,疑点排除,错误不在【执行计划】处
 
2008-05-14晚      凌蓝风