首页 > 数据库 > Oracle >

基于oracle的sql优化方法论

2015-06-20

Oracle数据库里SQL优化的终极目标就是要缩短目标SQL语句的执行时间。要达到上述目的,我们通常只有如下三种方法可以选择: 1、降低目标SQL语句的资源消耗; 2、并行执行目标SQL语句; 3、平衡系统的资源消耗。 “

Oracle数据库里SQL优化的终极目标就是要缩短目标SQL语句的执行时间。要达到上述目的,我们通常只有如下三种方法可以选择:
1、降低目标SQL语句的资源消耗;
2、并行执行目标SQL语句;
3、平衡系统的资源消耗。
“方法1:降低目标SQL语句的资源消耗”以缩短执行时间,这是最常用的SQL优化方法。这种方法的核心是要么通过在不更改业务逻辑的情况下改写SQL来降低目标SQL语句的资源消耗,要么不改SQL但通过调整执行计划或相关表的数据来降低目标SQL语句的资源消耗。
方法2:并行执行目标SQL语句”,这实际上是以额外的资源消耗来换取执行时间的缩短,很多情况下使用并行是针对某些SQL的唯一优化手段。
“方法3:平衡系统的资源消耗” 可以避免不必要的资源争用所导致的目标SQL语句执行时间的增长。对某些SQL而言,其执行时间的增长或者不稳定(时快时慢)不是由于其执行计划的问题,而是由于在目标SQL执行时系统处于高负荷状态,执行目标SQL所需要的硬件资源(CPU、内存、IO等)得不到保障,存在争用的情况。在这样的情况下,如果我们能平衡系统的资源消耗,把一些跟目标SQL同时执行的不重要但很消耗系统资源的操作(如报表、批处理等)挪到系统不忙的时候执行,比如把它们挪到晚上执行,那么就可以释放部分系统硬件资源以确保目标SQL执行时所需要的硬件资源不再存在争用的情况,进而就避免了其执行时间的增长或者不稳定。


对于有问题的SQL做SQL优化的具体步骤一般为:
1、查看该SQL语句的执行计划,并结合其资源消耗情况和相关统计信息、Trace文件来分析其执行计划是否合理;
2、通过修正措施(如调整该SQL的执行计划等)来对该SQL做调整以缩短其执行时间,这里调整的指导原则就是之前刚介绍的Oracle数据库里做SQL优化通常会采用的三种方法。





统计信息的收集如何来做比较合理?
答:在Oracle数据库里,收集统计信息总的原则就是量体裁衣,即要找到适合自己系统的统计信息收集策略,用尽量小的代价收集到能稳定跑出正确执行计划的统计信息即可,也就说收集到的统计信息不一定要特别准,只要具备代表性,能稳定跑出正确的执行计划就可以了。
根据上述指导原则,我们建议采用如下的收集策略:根据系统的类型及特点来决定是采用Oracle自带的自动统计信息收集作业还是自己写shell脚本来收集统计信息。对于那种数据量不大的OLTP类型的系统,建议是用前者。如果是数据量很大的OLAP或者DSS系统,建议自己写shell脚本来收集统计信息,因为对于这种系统而言,很多表的访问都是只有全表扫描这一种方式,这种情况下这些表的统计信息是否准确就无所谓了,甚至无须浪费资源和时间来对这些表收集统计信息,而是写shell脚本仅对那些需要收集统计信息的表和相关对象收集统计信息就可以了。
不管是采用自动统计信息收集作业还是手工写Shell脚本来收集统计信息,我们都需要特别注意如下注意事项:
(1)当系统中导入了大量数据后,如果要马上进行相关的业务处理,建议在进行相关的业务处理之前及时对相关表手工收集统计信息,因为这些被导入了大量数据的表已经来不及等到当天的自动统计信息收集作业自动收集统计信息了。
(2)某些新上线或新迁移的系统,其中的某些表在上线/迁移之初其数据量为0(所以其相关统计信息也为0),接着在导入少量数据(比如几千条)后马上进行相关的业务处理,建议在进行相关的业务处理之前及时对这些表手工收集统计信息。因为一来这些被导入了少量数据的表已经来不及等到当天的自动统计信息收集作业自动收集统计信息了,二来这些表的统计信息的初始值为0,所以即便随后导入的数据量并不大,但如果不及时收集统计信息的话也可能也会对相关SQL的执行计划产生颠覆性的影响。
这里需要注意的是:无论是用ANALYZE命令还是用DBMS_STATS包来收集统计信息,它们均会提交当前事务。如果应用对事务有强一致性的要求,同时在当前事务中在导入数据后又必须得在同一个事务中进行相关的后续业务处理,则可以在后续处理的相关SQL中加入Hint(或者使用SQL Profile/SPM来替换相关SQL的执行计划)以让Oracle走出理想执行计划而不再受统计信息正确与否的干扰。
(3)建议及时对包含日期型字段的表收集统计信息,避免谓词越界现象的出现。自动统计信息收集作业选取收集对象的标准,在默认情况下是目标表的delete+insert+update操作所影响的记录数已经超过了TAB$中记录的目标表总记录数的10%,或者是自上次自动统计信息收集作业完成之后目标表被执行过truncate操作。这种标准对于有日期型字段的大表而言,可能会显得过于宽松。
比如一个有日期型字段的表,每天向该表中导入当天的数据,同时该表中的数据保持一年,那么一年后该表新导入的数据仅占该表总记录数的1/365。这种情况下该表的那个日期型字段的统计信息很可能就是不准的,此时就非常容易出现谓词越界的现象,进而可能会严重影响相关SQL的执行计划。这种情况下我们的应对方法就是手工写shell脚本单独对该表每天收集统计信息,或者使用DBMS_STATS.SET_TABLE_PREFS来对自动统计信息收集作业中针对该表的收集设置做修改,比如用DBMS_STATS.SET_TABLE_PREFS将针对该表的STALE_PERCENT的值设为0(仅适用于Oracle 11g及其以上的版本)。
(4)收集统计信息的采样比例到底设为多少合适是一个永恒的话题,实际上这个问题是没有标准答案的,因为这个采样比例取决于执行收集统计信息作业被允许消耗的时间和系统的数据分布情况。如果时间允许,我们当然是希望将采样比例设为100%,因为这时收集到的统计信息肯定是最准的,但100%的采样比例所带来的问题就是收集统计信息作业的执行时间可能会非常长,甚至在规定的窗口期内跑不完。如果采样比例不能设为100%,那么设为多少合适则取决于系统数据的分布情况。例如,如果系统的数据分布十分均匀,则很可能用10%的采样比例所得到的统计信息就已经能让目标SQL跑出正确、稳定的执行计划了。也就是说,这种情况下得到的执行计划很可能和用100%的采样比例得到的执行计划是一样的,既然如此,为什么还要耗费额外的时间与资源去跑增加采样比例后的统计信息收集作业?但如果数据分布不均匀,此时同样采用了10%的采样比例,并且碰巧采样的这10%的数据块大多数都是空块或者是数据分布极不均衡、不具备代表性的数据块,则这种情况下得到的统计信息很可能就是不准的,甚至是错误的,也就是说这个时候就需要增加采样比例了。面对后一种情形,采样比例增加到多少合适呢?是20%,30%,50%还是更多?没有人可以告诉你答案,也许只能靠你自己不断的摸索和调整,直到收集统计信息作业既能在规定的窗口期内跑完同时目标SQL又能跑出正确的执行计划为止。
尽管如此,我们还是推荐一个统计信息收集作业采样比例的初始值:对于Oracle 11g及其以上的版本,收集统计信息的采样比例建议采用DBMS_STATS.AUTO_SAMPLE_SIZE。Oracle 11g中的AUTO_SAMPLE_SIZE采用了全新的哈希算法,它既能保证在较短的时间内执行完又能保证收集到的统计信息的质量(接近采样比例为100%时的质量)。如果是Oracle 10g,继续采用DBMS_STATS.AUTO_SAMPLE_SIZE就不太合适了,因为这个自动采样比例在Oracle 10g里是非常小的一个值,我们建议在Oracle 10g中将采样比例的初始值设为30%,然后根据目标SQL的实际执行情况再做调整;如果是Oracle 9i,我们建议将采样比例的初始值也设为30%,然后根据目标SQL的实际执行情况再做调整。
(5)建议使用DBMS_STATS包来对分区表收集全局统计信息,并且收集分区表的统计信息时应使用一致的GRANULARITY参数,全局统计信息的收集方法和注意事项请见《基于Oracle的SQL优化》的5.6节“全局统计信息”。
(6)建议要额外收集系统统计信息,但系统统计信息只收集一次就够了,除非系统的硬件环境发生了变化,系统统计信息的收集方法请见《基于Oracle的SQL优化》的5.9节“系统统计信息”。
(7)建议要额外收集X$表的内部对象统计信息,但仅仅是在明确诊断出系统已有的性能问题是因为X$表的内部对象统计信息不准的情形下,对于其他情形就不要收集了。X$表的内部对象统计信息的收集方法和注意事项请见《基于Oracle的SQL优化》的5.11节“内部对象统计信息”。
如果是用写shell脚本的方式手工收集统计信息,我们推荐采用如下的方式。
(1)对于单表的统计信息收集,建议初始情况下采用如下的方式(然后根据目标SQL的实际执行情况再做调整):
(a)适用于Oracle 11g及其以上的版本
EXEC DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'SCHEMA_NAME',
TABNAME => 'TABLE_NAME',
ESTIMATE_PERCENT => DBMS_STATS.AUTO_SAMPLE_SIZE,
CASCADE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE REPEAT' );


(b)适用于Oracle 9i/10g
EXEC DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'SCHEMA_NAME',
TABNAME => 'TABLE_NAME',
ESTIMATE_PERCENT => 30,
CASCADE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE REPEAT' );
(2)对于单个schema的统计信息收集,建议初始情况下采用如下的方式(然后根据目标SQL的实际执行情况再做调整):
(a) 适用于Oracle 11g及其以上的版本
EXEC DBMS_STATS.GATHER_SCHEMA_STATS(
OWNNAME => 'SCHEMA_NAME',
ESTIMATE_PERCENT => DBMS_STATS.AUTO_SAMPLE_SIZE
CASCADE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE REPEAT' );


(b) 适用于Oracle 9i/10g
EXEC DBMS_STATS.GATHER_SCHEMA_STATS(
OWNNAME => 'SCHEMA_NAME',
ESTIMATE_PERCENT => 30
CASCADE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE REPEAT' );
无论是用Oracle自带的自动统计信息收集作业还是用我们自己写的shell脚本来收集统计信息,对于直方图统计信息的收集策略,我们建议都采用如下的方式:只对已经存在直方图统计信息的列重复收集直方图统计信息,而目标列的初次直方图统计信息的收集则是由了解系统的DBA手工来做。具体来说就是这样:
(1)设置METHOD_OPT的值为‘FOR ALL COLUMNS SIZE 1’后先收集一次统计信息,这意味着删除了所有列上的直方图统计信息。
(2)在已经删除了所有列上的直方图统计信息后,设置METHOD_OPT的值为‘FOR ALL COLUMNS SIZE REPEAT’,这意味着今后将只对已经存在直方图统计信息的列重复收集直方图统计信息。
(3)在系统使用或调优的过程中,目标列的初次直方图统计信息的收集是由了解系统的DBA手工来做。
上面所汇总的注意事项和推荐收集方法并没有涵盖所有的方面,完全可以在了解上述知识点的基础上制订出适合于自己系统的统计信息收集策略。比如,如果某个表的数据变化得特别频繁,我们完全可以将其统计信息锁住(使用DBMS_STATS.LOCK_TABLE_STATS,适用于Oracle 10g及其以上的版本),从此不再花时间对其收集统计信息,当然这里的前提条件是锁住的统计信息能使访问该表的SQL走出理想的执行计划。
总之,如何正确的收集统计信息是一个复杂的话题,需要注意的知识点也有很多,但原则只有一个:量体裁衣,找到适合自己系统的统计信息收集策略,用尽量小的代价收集到能稳定跑出正确执行计划的统计信息即可。


某个很耗资源的SQL依然执行很快,要么是用了并行,要么是用了类似于Exadata这样的分布式计算框架。此时如果硬件资源还能撑住,那么再优化的意义并不大(而且很多情况下,特别是在OLAP的环境下,并行和使用Exadata这样的分布式计算框架是针对某些SQL的唯一优化手段)。


我的学习方法:
1、我在metalink上至少已经看过5000篇文章,所以对问题还是有一定的敏感度。
2、这个是基于我比较熟悉Oracle数据库,另外就是metalink和DSI对我的帮助非常大,有metalink和DSI的基础,有一些问题我在模拟的过程中确实可以做到信手拈来。
3、BBED只是一个工具,用好它的关键在于要了解Oracle数据库的一些internal知识点和数据块结构,这才是我们需要深入钻研的地方,深入研究BBED这个工具没有太大意义。


如何通过metalink获取帮助:


第一、怎样能够通过metalink提高自己的水平?(如何通过metalink来学习呢)
如果研究Undo那么就看一个系列的undo文章 包括ORA-1555处理等等, 通过系列文章来学习


第二、metalink中是否有 为查询 各种指标而写好的 SQL脚本?我该如何才能找到这些?
使用script 做关键词 可以获得绝大多数这些脚本
例如undo script




1、metalink是我最频繁逛的网站,没事我就在上面晃
2、不是,DSI我只是反复的看,实验我做的不多(我只有在觉得不太对的时候才会去做实验验证)
3、我用工具或脚本不多,给不了你什么推荐(推荐用sosi.txt看统计信息,这个我强烈推荐,呵呵)。Tanel Poder的网站上有很多很有用的脚本,你可以去看看










1、我个人是两种方式兼有,先是在备份恢复方面钻研的相对深入,后续又在优化器方面做了较深入的钻研,同时从06年开始,我一直坚持在MOS上看Oracle数据库各个方面的文章(这应该算是横向的学习)。
2、Jonathan Lewis的《Cost Based Oracle Fundamentals》的第4章“Simple B-tree Access”中详细说明了Effective Index Selectivity和Effective Table Selectivity的算法,你可以去看一下。
3、IN-List Expansion / OR Expansion的好处是这样改写成以UNION ALL连接的各个分支后,各个分支就可以各自走索引、分区修剪(Partition Pruning)、表连接等相关的执行计划而互不干扰;它的坏处是原先优化器只需要解析未做IN-List Expansion / OR Expansion之前的一个目标SQL并决定其执行计划就好了,而一旦做了IN-List Expansion / OR Expansion,优化器就要对等价改写后的每一个UNION ALL分支都执行同样的解析、决定其执行计划的工作,也就是说针对等价改写后的目标SQL的解析时间会随着UNION ALL分支的递增而递增。这意味着当IN后面的常量集合所包含的元素数量非常多的时候,IN-List Expansion / OR Expansion光解析的时间可能就会非常长,这就是我们在《基于Oracle的SQL优化》的“4.9.1 IN-List Iterator”中曾经提到过的IN-List Iterator的执行效率通常会比IN-List Expansion / OR Expansion的效率要好的原因。正是基于上述原因,做了IN-List Expansion / OR Expansion的等价改写SQL的效率并不一定会比原SQL要好,这也就意味着IN-List Expansion / OR Expansion一定也是基于成本的,即只有当经过IN-List Expansion / OR Expansion后的等价改写SQL的成本值小于原SQL的成本值时,Oracle才会对目标SQL执行IN-List Expansion / OR Expansion。
4、我在《基于Oracle的SQL优化》的“5.5.3.4.2 直方图对可选择率的影响”中详细说明了各种情况下density的计算方法并全部给出了实例,你可以去看一下。
5、Oracle计算并行成本的公式我也不确定,似乎是没有公开过。CBO在计算成本的时候本来就没有考虑缓存对物理I/O的影响,这也是CBO的局限性之一。
6、如果是因为聚簇因子的值而导致目标SQL没有走相关的索引并且你又不能重建表,则你可以使用manual类型的sql profile或者SPM固定目标SQL的执行计划;
7、没有固定的SQL调优方法,这是我在《基于Oracle的SQL优化》这本书里反复强调的
8、最难调的是那种多表关联并且SQL文本极其复杂的SQL,此时你必须要结合执行计划和具体的业务知识来调整,也就是说面对这样的SQL,首先你自己得知道应该走什么样的执行计划。
9、先仔细的从头到尾看一遍《基于Oracle的SQL优化》,然后有任何疑问都可以找我讨论





1、你这个问题太大了。简单来说就是这样:对执行计划的调整没有固定的策略,调整的手段是什么,能否奏效,均取决于你对CBO和执行计划的了解程度。
我曾经在20#提到:“方法1:降低目标SQL语句的资源消耗”以缩短执行时间,这是最常用的SQL优化方法。这种方法的核心是要么通过在不更改业务逻辑的情况下改写SQL来降低目标SQL语句的资源消耗,要么不改SQL但通过调整执行计划或相关表的数据来降低目标SQL语句的资源消耗。
方法1所涉及到的这两种优化手段在Oracle数据库中能否奏效以及效果的好坏与否很大程度上取决于对CBO和执行计划的理解程度,对CBO和执行计划理解的越深,这两种优化手段的应用就会越纯熟,效果就会越好。这也是《基于Oracle的SQL优化》这本书所要提出的Oracle数据库里SQL优化方法论的第一点:Oracle里SQL优化的本质是基于对CBO和执行计划的深刻理解。
实际上,《基于Oracle的SQL优化》这本书里基本上用了一整本书的篇幅来阐述上述SQL优化方法论的第一点内容。
2、有可能不需要你定位,因为你也许早就知道要调整的目标SQL是什么。如果你不知道待调整的目标SQL是什么,TOP SQL就是你首先要关注的调整目标。在Oracle数据库里,定位TOP SQL通常所采用的方法就是查看AWR报告或者Statspack报告,从AWR报告里的“SQL ordered by Elapsed Time”、“SQL ordered by CPU Time”、“SQL ordered by Gets”等部分就能清晰的定位出在采样的时间段内执行时间最长、消耗系统资源最多的Top SQL。





1、你的思路没问题,SQL调优不能脱离实际的业务,是的,Oracle数据库里大部分SQL优化的问题都可以通过增加或者减少索引的方式来解决,但这绝不是全部!实际上,《基于Oracle的SQL优化》这本书里列举的SQL优化实例基本上没有一个是简单的通过增删索引就能搞定的。SQL优化遇到困难时不知道如何继续本质还是在于积累不够,这方面没有快速提高水准的方法。你可以试着认真看完《基于Oracle的SQL优化》,看看这本书能否缓解你这方面的问题,如果还是不行,你再来和我讨论。
2、你这又是一个非常大的问题,而且表怎么设计跟具体业务会紧密相关,我能想到的考虑因素包括但不限于:要满足基本范式的要求(可能会局部反范式,以空间换时间);要考虑具体字段类型的设计(能用varchar2就不要用char等);如果涉及到lob字段,则要考虑lob字段的平均长度以及如何存储等因素;要考虑是否需要建约束和外键(子表的外键列上一定要建索引);要考虑是否需要分区(不是说大表就一定要分区);如果需要分区,分区方案是什么;要设计适当的归档和拆分机制实现表数据的动静分离,避免表内数据的无限增长,维持表内活动数据始终在一个可控的范围内……




怎么查看并行的执行计划:
用select * from table(dbms_xplan.display_cursor('sql_id/hash_value', child_cursor_number, 'advanced')),要指定SQL_ID和Child Cursor Number。
另外注意,当对并行执行的SQL使用GATHER_PLAN_STATISTICS hint时,FORMAT参数得是'ALLSTATS ALL',不能是常规的'ALLSTATS LAST'。




提问:
再次咨询您一个问题,我觉得优化好sql的话要对sql语句的背景(设计到业务)有了解, 我记得我们的系统中有很多sql语句中设计到了临时表,由于临时表没有统计信息,这时 有时临时表中的数据量的不同可能导致相同的sql语句执行效率会有很大的不同,在设计到 临时表时你对这条sql语句的优化思路是怎么样?
还有一个实际工作中的问题,有时一条很简单的sql查询语句,有选择性比较高的索引, 但是这样的sql可能会返回10条以内的记录数,我看到这样的sql语句的逻辑读会有几十,我 重构表降低索引的聚簇因子后相应的逻辑读会降下来,重构表降低索引的聚簇因子 你在实际的工作中会经常做吗?


1、确实是这样。对临时表的使用我一般是这样:1、如果业务对事务没有强一致性要求,则我在使用临时表之前会手工收集统计信息;2、无论是用ANALYZE命令还是用DBMS_STATS包来对临时表收集统计信息,它们均会提交当前事务。所以如果应用对事务有强一致性的要求,同时在当前事务中在导入数据后又必须得在同一个事务中进行相关的后续业务处理,则可以在后续处理的相关SQL中加入Hint(或者使用SQL Profile/SPM来替换相关SQL的执行计划)以让Oracle走出理想执行计划而不再受统计信息正确与否的干扰;3、94#提到的使用动态采样也是一种方法


2、通过重构表来降低聚簇因子的方法我不常用。在Oracle数据库中,能够降低目标索引的聚簇因子的唯一方法就是对表中数据按照目标索引的索引键值排序后重新存储。这里需要注意的,这种按某一个目标索引的索引键值排序后重新存储表中数据的方法确实可以降低该目标索引的聚簇因子的值,但可能会同时增加该表上存在的其他索引的聚簇因子的值。


表和索引的碎片,在什么时间点整理比较好,对性能改善比较大?
一般来说是这样:如果你在建表空间时指定了uniform size,extent级别的碎片基本上可以说就没有了。如果不是uniform size,你可以将受碎片问题困扰的表和索引迁移到uniform size的表空间(如用在线重定义等手段),迁移的时机应该是选择在系统不那么忙的时候做。







2009532140 发表于 2013-12-13 15:34
你说的范式,这个我知道数据库理论教材中看过..
但实际运用上,我咋感觉基本没人关注这个问题呢?
再者 …


你的感觉不一定是事实。


char是定长的,varchar2是变长的,同样定义一列,如果是用char(10),则存储'0'会占用10个byte;如果是用varchar2(10),则存储'0'只会占用1个byte,你觉是用哪个更省存储空间?
SQL> create table t1(c1 char(10),c2 varchar2(10));


Table created


SQL> insert into t1 values('0','0');


1 row inserted


SQL> commit;


Commit complete


SQL> select lengthb(c1),lengthb(c2) from t1;


LENGTHB(C1) LENGTHB(C2)
———– ———–
10 1


是,长度变化确实有可能会导致行迁移,但不能因此就不用varchar2。实际上,行迁移是在所难免的,我们也不能因噎废食。
相关文章
最新文章
热点推荐