对于一个数据库系统而言,sql执行计划的稳定性对于整个应用的稳定性和健壮性的重要性是无须赘言的.这里不去探讨outlines,sql profiles,sql plan baselines这样一些固定执行计划的技术,而是探讨一些影响sql执行计划稳定性的因素以及为了稳定执行计划我们应该如何去做.
一个sql语句的执行计划是oracle优化器的输出,那么oracle优化器的输入因素包括哪些呢?我觉得应该来说包括四个输入因素:
1.sql语句引用到的对象的物理模式.
2.sql语句引用到的对象的优化器统计信息.
3.sql执行时的优化器环境参数设置.我这里把系统统计信息也算在这里了
4.sql语句本身.
我们如果能够稳定住优化器的输入因素,也自然就可以稳定住优化器的输出部分,也就是说稳定住sql语句的执行计划.我们下面就从这4个因素着手讨论:
一.sql语句引用到的对象的物理模式
sql语句引用到的表是普通堆表,还是分区表,还是IOT表,有哪些索引等等这些物理模式无疑将影响sql的访问路径,影响sql的执行计划,执行效率.这里不去探讨具体某种模式适用的场景,那是一个太大的话题了,这里谈谈索引的添加删除时应该注意的事项.
我们知道索引的添加或者删除影响的不仅仅是当前要调整的sql,也要影响相关的所有sql的,所以添加或者删除索引的时候,不仅仅要考虑对这个sql的影响,也要考虑对其它sql的影响.
就算是针对当前的这个要调整的sql,一个索引的添加带来的影响也不是一定就可以预测的到的,这时你可以利用11g的invisible index特性,创建这个索引为invisible的,然后在一个特定的会话里alter session set OPTIMIZER_USE_INVISIBLE_INDEXES=true;然后查看这个索引的添加是否可以为这个sql带来积极的影响.当然,这个索引的添加对于其它相关sql的影响也就是说对于整个系统的影响其实也是不确定的,并不一定就会带来积极的影响,所以还是要求你得熟悉应用系统,熟悉系统频繁调用的,对系统性能至关重要的sql,在这个特定的会话里查看这个invisible的index对于这些sql的影响,然后决定是将这个索引修改为visible的,让它对于正常cbo可见,还是删除这个无用的甚至是有害的索引.
我们知道索引的维护是存在代价的,所以索引的存在会对dml操作带来影响,如果系统中存在着大量的索引的时候更是这样,这时候我们就需要删除一些根本就不会用到的索引,这时候alter index index_name monitoring usage;然后查看v$object_usage这个视图看看这个索引是否被使用到了,注意这里的监控一定要覆盖整个的sql使用周期,比如说有些sql只是在一个月的月底的时候才会执行,只有这时候才会使用某个索引,那么这样的时间段也要包括砸监控时间段内,如果在整个的监控周期内,这个索引从来就没有使用过,当然就可以把这个索引删除掉了.当然使用这种方法需要注意的一个问题是,这里并不记录这个索引的使用频率,也就是说它被使用了多少次,即使这个索引只是在你的一个测试语句中被使用了一次,它也是被记录为使用过的,而它只使用了一次这个信息你是不能从这个视图知道的.
另一个查看索引是否被使用过的信息源就是:v$sql_plan,dba_hist_sql_plan这两个视图,注意v$sql_plan只保留了现在还在共享池中的游标的信息;dba_hist_sql_plan和你awr数据保留的时间周期有关,另外它只保留了前n个(可以调整)符合各个标准(比如说执行次数,cpu时间消耗,逻辑读等)的sql的执行计划,但对系统性能重要的sql的执行计划应该是都保留了.从它们可以知道某个索引是否被使用了,而v$sql,dba_hist_sqlstat里记录了游标的执行统计信息,当然也包括它的执行次数,这样索引是否经常被使用也就可以确定.另外v$segment_statistics,DBA_HIST_SEG_STAT里记录了段的执行统计信息,当然也包括索引的使用信息,也可以作为一个参考数据的.
另一个有用的手段就是设置这个索引为invisible的,然后查看它对于整个系统性能上的影响,在覆盖了整个的sql使用周期之后,决定是删除掉这个索引,还是重新设置这个索引为visible的,这样可以避免删除掉某个索引之后,发现它是有用的,又重新建立它的代价,因为对于大型的数据库来说,可能建立一个索引的代价是很高的,不仅仅是时间上的消耗,还有创建周期内对DML操作的阻塞(online操作在它的开始和结束的短时间内还是会对表放置TM4的锁,还是会阻塞DML操作).
二.sql语句引用到的对象的优化器统计信息
统计信息的收集策略,这对于数据库系统的稳定性也是不言而喻的.至于具体的收集策略,需要针对你的应用,针对你的数据库系统做出自己的选择.个人觉得10g推出的自动的收集策略还是可以作为你的收集策略的一部分的.如果觉得它执行的时间不合适(比如说晚上10点可能正是你系统繁忙的时候),你可以disable掉GATHER_STATS_JOB,然后自己定制调度DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC();来完成一般对象的统计信息的收集工作.
当然需要意识到10g中这个job的默认选项存在的一些问题:比如说estimate_percent默认采样百分比很小,可能会带来一些问题,你可以调用Dbms_Stats.set_param来修改它的默认值,我们的一些系统,对象都不大的话,我就把它们的默认值都改为了100,当然这个需要你根据你们系统的实际情况作出调整. 再比如说method_opt的默认值for all columns size auto普遍存在的问题是收集了很多不必要的柱状图统计信息(可能只是因为极个别的sql语句的执行,比如说只是一些测试语句,oracle意识到了这种负载,意识到收集直方图信息对这些sql的执行是好的),也存在一些应该收集直方图信息却没有收集直方图信息的情况.据说(没有实证过,只是看文档上是这么说的)11g的默认收集策略在这两方面有了大的改进,estimate_percent默认采样比例很大,但因为算法上的改进(由sort改为了hash),执行时间上反而更快了,在直方图的采集上也更为合理了.10g时默认数据修改量达到10%之后才重新收集新的优化器统计信息的,11g的时候可以调整这个百分比了.可能这个10%的比例对你来说太大了,在到达这个百分比之前可能系统已经因为统计信息陈旧性能上可能已经在变坏了.所以一切都需要测试,测试什么样的收集策略对你的系统来说才是最好的.
上面说到可以把oracle的自动收集策略作为你的收集策略的一部分,让它来完成一般对象的统计信息的收集工作,你还可以在此之外定制一些自己的收集策略,就是dbms_stats.lock_table_stats锁定某些对象的统计信息,这样自动收集策略收集对象统计信息时就会跳过这些对象统计信息的维护工作,然后你单独定制这些对象的信息收集策略,比如说系统核心的业务对象,你可以根据业务情况具体的定制它们的收集选项,比如说采样大小,哪些列收集什么样的直方图信息,哪些列只收集基本的列统计信息,它们的调度策略,是定时的,还是你根据业务情况,数据增长变化情况每次都是手工的收集它们的统计信息等.对于一些需要在统一的收集策略之外单独收集的对象统计信息也可以这样收集,比如说系统默认的采样大小是100%,但你这个对象太大了,而且确实30%的采样比例足够了,你可以把它单独处理,再比如说数据变化量达到10%时才收集统计信息对它来说时间太长了,可能变化量达到3%的时候就必须重新收集对象统计信息了,你也可以单独收集它的统计信息收集工作.
另外也要注意这些策略之外的东西,比如说你对数据情况数据分布情况,对应用系统如何使用这些数据太清楚了,你完全可以手工的设置优化器统计信息,而不需要调用dbms_stats包来完成收集工作的,这样资源消耗要少很多,而且更能反映真实的数据情况.在手工的加载了大量数据之后,立即手工收集优化器统计信息.
另外也可以把动态采样作为你的收集策略的一个补充部分.
关于这个,个人觉得可以参考一下laoxiong在《ORACLE DBA手记》里的一篇文章,而国外大牛们的博客,书里很多都要提到的Karen Morton的一篇文章《managing statistics for optimal query performance》,我倒觉得没写些什么东西.
当然,我觉得有一点特别重要,需要意识到:you know your data and how your applications use it(often better than Oracle does).所以你需要针对你的系统,针对它的使用做出一些有针对性的工作,找到对你的系统最好的收集策略,收集选项,当然这个过程中需要测试,反复不断的测试,也需要针对应用的改变,升级,数据量的变化不断的做出调整.这些工作注定是繁琐的,但对于系统的稳定却是至关重要的。
三.sql执行时的优化器环境参数设置.我这里把系统统计信息也算在这里了
我觉得对于这些参数值来说,没有一个万能的设置值,适用于所有的系统.如果有的话,ORACLE也早就把这些值写死在内部代码中了,不会留下这样的接口来让你调整了.如果有些参数确实需要调整的话,需要针对自己的系统不断的设置测试直至找到一个最优值,不要随意的把网上的一个非默认值就认为是应该设置的值.另外也不要因为这个系统中这个参数设置值表现不错,便不经测试的把这个设置值应用到自己的另一个系统中.
不同的数据库版本中,一些优化器相关参数的默认值会有变化,一些优化器行为会有所不同,数据库的升级给你带来的可能不仅仅是正面的影响,也可能会给系统性能带来不利的影响,在升级前需要对数据库的新特性有充分的了解和认识,也要充分的测试应用在新版本数据库下的性能表现,最终来决定是否升级等问题.
对象的统计信息影响的只是相关的一些sql的性能,而优化器相关的参数设置影响的范围可能要更广一些,所以优化器相关的参数调整要更谨慎一些,需要经过严密的测试工作.如果只是需要对有限的sql使用一些和系统设置不一样的参数设置的话,可以在相关的sql语句中添加opt_param提示来解决这个问题.
我这里之所以要把系统统计信息算在这里,是因为它的影响像这些优化器参数的设置改变影响一样大,当然,更确切的说,系统统计信息的改变影响范围更大一些,因为它影响的是整个的CBO代价模型的计算,所以它的调整更要经过严格的测试工作之后才能在生产库上付诸实施.
四.sql语句本身
我们知道优化器在一些情形下还不足够智能,一些实际上等价的sql形式,优化器可能意识不到它们是等价的,在处理上可能会有所不同,性能表现上可能也会有所不同,比如说select max(id) max_id,min(id) min_id from test和select max_id,min_id from (select max(id) max_id from test),(select min(id) min_id from test)实际上是等价的,但优化器的处理却是有所不同的,后者的性能表现要好一些,我们需要知道在某些版本下哪种写法性能更好一些,在这样的数据库版本下尽量这样去写(当然,可能随着数据库版本的升迁,可能到某个版本的时候,它们的表现已经完全相同了).当然这里不去讨论这个问题,因为这里的sql实际上已经变化了.我们这里要讨论的是sql不变的情形下,一些因素带来的执行计划不稳定性.
比如说sql语句没变,但它引用的视图的定义改变了.再比如说sql语句中使用了绑定变量的情形,在9i之前,在遇到sql语句中使用绑定变量的情形时,硬分析时在确定card,确定选择性时只能1%,5%这样的默认选择性(相关列上连基本的列统计信息都没有收集)或者是1/num_distinct,density这样的选择性(相关列上收集了基本的列统计信息).列上数据分布不均匀的情况下,你收集了直方图信息,可优化器在确定选择性时却也无法利用这些信息,这无疑也会带来一些性能上的问题.所以9i的时候引入了一个绑定变量peeking的机制,在硬分析时peeking这次的绑定变量值,决定执行计划,此后不再peeking值,而是一直使用这样的执行计划.这样可能就会带来一些性能上的问题,这个执行计划对这些值是好的,可能对于另一些值就是坏的,说到底不同的值就不应该使用相同的执行计划,如果硬分析时peeking到的刚好是非典型输入值的话,对这个sql的整体性能表现来说可能就是坏的.另外因为各种因素,这个sql重新硬分析的时候,也带来了执行计划的不稳定性.11g为此引入了自适应的游标共享机制,在一定程度上解决了这个问题.但在10g的时候还要面对这个问题,我觉得一种解决方案就是分支处理,比如说
if (v_catalogid in(122,291,754)) then --一些倾斜的数据值
open v_sql for select /*more*/....where catalogid=v_catalogid;
else
open v_sql for select /*less*/....where catalogid=v_catalogid;
end if;
这样在catalogid列上仍然收集直方图信息,并且要通过直方图信息让优化器知道122,291,754这些值确实是数据倾斜的.这样处理之后只是两个sql语句,仍然使用绑定变量,既使用了直方图信息生成了两个不同的执行计划,避免了绑定变量peeking带来的性能问题,又避免了全部使用字面值对于共享池的冲击.
当然,我觉得无论出于什么目的,都应该保留核心的业务sql正常表现时的执行计划,执行统计信息,当时的优化器统计信息,当时的物理模式,比如说有哪些索引等,以便于在性能出现问题的时候能够快速的诊断解决问题.可以手工保留,我觉得还是应该保留这样的一些信息在数据库之外的好.另外也要通过一些数据库的手段,比如说awr数据保留一些baseline数据于它的保留周期之外,awr数据也可以保留几个月甚至更长的时间,因为它除了对于sysaux表空间的消耗之外几乎是不存在其它的开销的,但这些数据对于性能问题的诊断快速解决可能却是至关重要的.
另外,10g开始在收集新的对象统计信息前,会自动保留当前的对象优化器统计信息到一些数据字典表里.这些历史数据的默认保留周期是30天,你完全可以根据系统需要设置的周期更长些,这样在出现性能问题并确定是统计信息的问题之后,可以通过复原这样的优化器统计信息来快速的解决问题(在性能出现问题的时候,可能手工的收集统计信息要消耗太长的时间,导致性能问题不能快速解决的,也正是因为这个原因,在你对数据了解的前提下,你也可以通过手工的设置优化器统计信息来解决这样的性能问题).另外你也可以通过这些数据字典表来查看以往时对象统计信息是什么样的,用来保存历史统计信息的表:
SELECT OBJECT_NAME FROM DBA_OBJECTS WHERE owner='SYS' and OBJECT_NAME LIKE 'WRI$_OPTSTAT_%';
WRI$_OPTSTAT_AUX_HISTORY
WRI$_OPTSTAT_HISTGRM_HISTORY
WRI$_OPTSTAT_HISTHEAD_HISTORY
WRI$_OPTSTAT_IND_HISTORY
WRI$_OPTSTAT_OPR
WRI$_OPTSTAT_TAB_HISTORY
另外,参数设置的变更,对象物理模式的表更,比如说索引的添加,删除等,都应该有相应的变更管理,文档化这些东西,这样在出现性能问题时才更容易快速的定位解决问题.