当前位置: 首页 > 科技观察

数据库性能优化的SQL语句优化(下)

时间:2023-03-17 12:25:43 科技观察

(1)选择最有效的表名顺序(只在基于规则的优化器中有效):ORACLE的解析器按照从右到左的顺序处理FROM子类表名语句中,FROM子句中写的最后一个表(基本表驱动表)会先被处理。FROM子句中有多个表时,必须选择记录数最少的表作为基表。如果查询连接的表超过3个,则需要选择交集表作为基表。交集表是指被其他表引用的表。(2)WHERE子句中的连接顺序:ORACLE采用自下而上的顺序解析WHERE子句。根据这个原则,表之间的连接必须写在其他WHERE条件之前,那些能过滤出最大记录数的条件必须写在WHERE子句的末尾。(3)SELECT避免在子句中使用'*':ORACLE在解析过程中会把'*'依次转换成所有的列名。这项工作是通过查询数据字典来完成的,这意味着需要花费更多的时间。(4)减少访问数据库的次数:ORACLE内部进行了大量的工作:解析SQL语句、估计索引利用率、绑定变量、读取数据块等。(5)重新设置SQL*Plus中的ARRAYSIZE参数,SQL*Forms和Pro*C可以增加每次数据库访问的检索数据量,推荐值为200。(6)使用DECODE函数减少处理时间:使用DECODE函数避免重复扫描同一条记录或重复连接到同一张表。(7)整合简单的、不相关的数据库访问:如果你有几个简单的数据库查询语句,你可以将它们组合成一个查询(即使它们之间没有关系)。(8)删除重复记录:删除重复记录最高效的方法(因为使用了ROWID)例子:DELETEFROMEMPEWHEREE.ROWID>(SELECTMIN(X.ROWID)FROMEMPXWHEREX.EMP_NO=E.EMP_NO)(9)用TRUNCATE代替DELETE:当删除表中的记录时,一般情况下,回滚段(rollbacksegments)用于存储可以恢复的信息。如果没有COMMIT事务,ORACLE会将数据恢复到删除前的状态(恢复到执行删除命令前的状态),当使用TRUNCATE时,回滚段不再存储任何可以恢复的信息。命令执行后,数据无法恢复。因此,调用的资源很少,执行时间会很短。(译者注:TRUNCATE只适用于删除整张表,TRUNCATE是DDL不是DML)。(10)尽可能的使用COMMIT:只要有可能,在程序中就尽可能的使用COMMIT,这样程序的性能会得到提升,同时由于COMMIT释放的资源而减少需求。COMMIT释放的资源:回滚段用于恢复数据的信息。b.程序语句获取的锁c.重做日志缓冲区中的空间d。ORACLE管理上述三种资源的内部成本(11)将HAVING子句替换为Where子句:避免使用HAVING子句,HAVING只会在检索完所有记录后才对结果集进行过滤。这个处理需要排序、合计等操作。如果WHERE子句可以限制记录数,则可以减少这种开销。(oracle中没有)on、where、having这三个可以加条件的子句中,on先执行,where在其次,having在最后,因为on是过滤前面不满足条件的记录进行统计,可以减少中间操作要处理的数据。按理说应该是最快的,where应该比having快,因为它在进行sum之前先过滤数据,只有在两表join的时候才用on。所以在一张表中,只有哪里与拥有相比较。在单表查询统计的情况下,如果要过滤的条件不涉及要计算的字段,那么他们的结果是一样的,但是where可以用rushmore技术,having不行,后者比较慢速度方面。如果涉及计算字段,则说明该字段的值在计算之前是不确定的。按照上一篇写的工作流程,where的动作时间是在计算之前完成的,having是在计算之后的。有效,所以在这种情况下,两者的结果会有所不同。多表连接查询时,on比where早工作。系统先根据表之间的join条件,将多个表组合成一个临时表,然后按where过滤,然后计算,计算完再用having过滤。可见,要让过滤条件发挥正确的作用,首先要了解条件应该在什么时候起作用,然后再决定放在哪里。(12)减少对表的查询:在包含子查询的SQL语句中,要特别注意减少对表的查询。例子:SELECTTAB_NAMEFROMTABLESWHERE(TAB_NAME,DB_VER)=(SELECTTAB_NAME,DB_VERFROMTAB_COLUMNSWHEREVERSION=604)(13)通过内部函数提高SQL效率:复杂的SQL往往牺牲执行效率。能够掌握以上使用函数解决问题的方法,在实际工作中是非常有意义的。(14)使用表的别名(Alias):在SQL语句中连接多个表时,请使用表的别名,并在每个Column前加上别名。这样,可以减少分析时间和那些由列歧义引起的语法错误。(15)ReplaceINwithEXISTSandNOTINwithNOTEXISTS:在很多基于基本表的查询中,为了满足一个条件,往往需要连接另一个表。在这种情况下,使用EXISTS(或NOTEXISTS)通常会提高查询效率。在子查询中,NOTIN子句将执行内部排序和合并。无论哪种情况,NOTIN都是效率最低的(因为它执行的是全表遍历)。为了避免使用NOTIN,我们可以将其改写为OuterJoins或NOTEXISTS。示例:(高效)SELECT*FROMEMP(基表)WHEREEMPNO>0ANDEXISTS(SELECT'X'FROMDEPTWHEREDEPT.DEPTNO=EMP.DEPTNOANDLOC='MELB')(低效)SELECT*FROMEMP(基表)WHEREEMPNO>0ANDDEPTNOIN(SELECTDEPTNOFROMDEPTWHERELOC='MELB')(16)识别'执行效率低下'的SQL语句:虽然各种SQL优化的图形化工具层出不穷,但是自己写SQL工具解决问题永远是最好的方法:SELECTEXECUTIONS,DISK_READS,BUFFER_GETS,ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2)Hit_radio,ROUND(DISK_READS/EXECUTIONS,2)Reads_per_run,SQL_TEXTFROMV$SQLAREAWHEREEXECUTIONS>0ANDBUFFER_GETS>0AND(BUFFER_GETS-DISK_READS)/BUFFER_GETS<0.8ORDERBY4DESC;(17)提高效率啦索引是表的概念部分,用于提高检索数据的效率。ORACLE使用复杂的自平衡B树结构。通常,通过索引查询数据比全表扫描要快。当ORACLE发现执行时ORACLE优化器在查询和更新语句的最佳路径时会使用索引。使用索引还可以在连接多个表时提高效率。使用索引的另一个好处是它提供了主键(primarykey)验证的唯一性。对于那些LONG或LONGRAW数据类型,您几乎可以索引所有列。通常,使用索引在大型表中特别有效。当然你也会发现在扫描小表的时候使用索引也可以提高效率。虽然使用索引可以提高查询效率,但是我们也要注意它的成本。索引需要存储空间和定期维护。每当在表中添加或删除记录或修改索引列时,索引本身也会被修改。这意味着每条记录的INSERT、DELETE、UPDATE将支付4、5倍的磁盘I/O。因为索引需要额外的存储空间和处理,那些不必要的索引实际上会拖慢查询响应时间。定期重建索引是必要的:ALTERINDEXREBUILD(18)ReplaceDISTINCTwithEXISTS:提交时包含一对多表信息(如部门表和员工表)的查询,避免在SELECT子句中使用DISTINCT。一般可以考虑用EXIST代替。EXISTS使查询速度更快,因为RDBMS核心模块会在满足子查询的条件后立即返回结果。示例:(低效):SELECTDISTINCTDEPT_NO,DEPT_NAMEFROMDEPTD,EMPEWHERED.DEPT_NO=E.DEPT_NO(高效):SELECTDEPT_NO,DEPT_NAMEFROMDEPTDWHEREEXISTS(SELECT'X'FROMEMPEWHEREE.DEPT_NO=D.DEPT_NO);(19)sql语句大写;因为oracle总是先解析sql语句,把小写字母改成大写再执行。(20)java代码中尽量少用连接符“+”连接字符串!(21)避免在索引列上使用NOT,通常我们要避免在索引列上使用NOT,NOT会产生与在索引列上使用函数一样的效果。当ORACLE“遇到”NOT时,他会停止使用索引,而是进行全表扫描。(22)如果索引列是函数的一部分,请避免在索引列上使用计算WHERE子句。优化器将使用全表扫描而不是索引。示例:效率低下:SELECT…FROMDEPTWHERESAL*12>25000;高效:SELECT…FROMDEPTWHERESAL>25000/12;(23)use>=insteadof>efficient:SELECT*FROMEMPWHEREDEPTNO>=4inefficient:SELECT*FROMEMPWHEREDEPTNO>3ofboth区别在于前者DBMS会直接跳转到DEPT等于4的第一条记录而后者会先找到DEPTNO=3的记录并向前扫描到DEPT大于3的第一条记录。(24)用UNION替换OR(适用于索引列)一般情况下,将WHERE子句中的OR替换为UNION会有更好的效果。对索引列使用OR将导致全表扫描。注意,以上规则只对多个索引列有效。如果有没有建立索引的列,查询效率可能会因为不选择OR而降低。在以下示例中,索引同时建立在LOC_ID和REGION上。高效:SELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHERELOC_ID=10UNIONSELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHEREREGION="MELBOURNE"低效:SELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHERELOC_ID=10ORREGION="MELBOURNE"如果坚持使用OR,需要返回最少记录和索引列先写吧。(25)用IN代替OR是一个简单易记的规则,但实际执行效果有待检验。在ORACLE8i下,两者的执行路径好像是一样的。低效:SELECT....FROMLOCATIONWHERELOC_ID=10ORLOC_ID=20ORLOC_ID=30高效SELECT...FROMLOCATIONWHERELOC_ININ(10,20,30);(26)AvoidusingISNULLandISNOTNULLonindexcolumns避免在索引列中使用任何可空值,ORACLE将无法使用索引。对于单列索引,如果该列包含空值,则该记录将不存在于索引中。对于复合索引,如果每列都为空,则该记录也不存在于索引中。如果至少一列不为空,则该记录将存在于索引中。例:如果在表的A列和B列上建立了唯一索引,表中有一条A、B值为(123,null)的记录,ORACLE将不接受下一条A相同的记录和B值(123,null)null)记录(插入)。但是,如果所有的索引列都是空的,ORACLE会认为整个键值都是空的,空不等于空。所以你可以插入1000条key值相同的记录,当然都是空的!因为索引列中不存在空值,WHERE子句中索引列的空值比较会导致ORACLE禁用索引。效率低下:(索引失败)SELECT...FROMDEPARTMENTWHEREDEPT_CODEISNOTNULL;高效:(索引有效)SELECT...FROMDEPARTMENTWHEREDEPT_CODE>=0;(27)总是使用索引的第一列:如果索引建立在多列上,只有在它的where子句引用索引的第一列(leadingcolumn)时,优化器才会选择使用索引.这也是一个简单但重要的规则。当只引用索引的第二列时,优化器使用全表扫描忽略索引。(28)ReplaceUNIONwithUNION-ALL(ifpossible):当SQL语句需要两个UNION的查询结果集时,会将两个结果集以UNION-ALL的形式合并,然后在输出最终结果前进行排序.如果您使用UNIONALL而不是UNION,则不需要这种排序。效率会提高。需要注意的是,UNIONALL会在两个结果集中重复输出相同的记录。因此,从业务需求分析的可行性来看,还是需要使用UNIONALL。UNION会对结果集进行排序,这个操作会使用SORT_AREA_SIZE的内存。这个内存的优化也很重要。下面的SQL可以用来查询排序的消耗情况。低效:SELECTACCT_NUM,BAL??ANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'UNIONSELECTACCT_NUM,BAL??ANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'高效:SELECTACCT_NUM,BAL??ANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'UNIONALLSELECTACCT_NUM,BAL??ANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'(29)用WHERE替换ORDERBY:ORDERBY子句只在两个严格的条件下使用索引。ORDERBY中的所有列必须包含在同一个索引中,并保持索引中的顺序。所有在ORDERBY中的列都必须定义为非空。WHERE子句中使用的索引和ORDERBY子句中使用的索引不能并行。例如:表DEPT包含以下列:DEPT_CODEPKNOTNULLDEPT_DESCNOTNULLDEPT_TYPENULL低效:(未使用索引)SELECTDEPT_CODEFROMDEPTORDERBYDEPT_TYPE高效:(使用索引)SELECTDEPT_CODEFROMDEPTWHEREDEPT_TYPE>0(30)避免改变索引列的类型:当比较不同数据类型的数据时,ORACLE自动对列进行简单的类型转换。假设EMPNO是一个数值类型的索引列。SELECT...FROMEMPWHEREEMPNO='123'其实ORACLE经过类型转换后,语句变成:SELECT...FROMEMPWHEREEMPNO=TO_NUMBER('123')幸好索引列没有发生类型转换,索引的目的没有改变。现在,假设EMP_TYPE是一个字符类型的索引列。SELECT...FROMEMPWHEREEMP_TYPE=123该语句被ORACLE转换为:SELECT...FROMEMPWHERETO_NUMBER(EMP_TYPE)=123因为内部类型转换,所以不会使用这个索引!为了避免ORACLE对你的SQL进行隐式类型转换,最好将类型转换显式表达出来。注意,当字符和数值进行比较时,ORACLE会优先将数值类型转换为字符类型如:selectemp_nameformemployeewheresalary>3000这条语句中,如果salary是Float类型,优化器会优化为Convert(float,3000),因为3000是一个整数,我们应该在编程时使用3000.0而不是在运行时等待DBMS进行转换。同样的字符和整型数据转换。(31)需要注意的WHERE子句:SELECT语句中的一些WHERE子句没有使用索引。这里有些例子。在以下示例中:(1)'!='将不使用索引。请记住,索引只能告诉您表中有什么,而不能告诉您表中没有什么。(2)'||'是一个字符连接函数。与其他功能一样,索引被禁用。(3)'+'是数学函数。与其他数学函数一样,索引被禁用。(4)相同的索引列不能相互比较,会造成全表扫描。(32)通常,使用索引比全表扫描快几倍甚至几千倍!A。如果检索的数据量超过表中记录的30%,使用索引并不能显着提高效率。b.在某些情况下,使用索引可能比全表扫描慢,但这是同数量级的区别。(33)避免使用耗费资源的操作:带有DISTINCT、UNION、MINUS、INTERSECT、ORDERBY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作,而其他的至少需要执行两次排序。通常,带有UNION、MINUS、INTERSECT的SQL语句可以用其他方式改写。如果你的数据库的SORT_AREA_SIZE调的好,用UNION,MINUS,INTERSECT也可以考虑,毕竟可读性很强。(34)优化GROUPBY:为了提高GROUPBY语句的效率,可以在GROUPBY之前过滤掉不需要的记录。以下两个查询返回相同的结果,但第二个查询显然要快得多。低效:SELECTJOB,AVG(SAL)FROMEMPGROUPbyJOBHAVINGJOB='PRESIDENT'ORJOB='MANAGER'高效:SELECTJOB,AVG(SAL)FROMEMPWHEREJOB='PRESIDENT'ORJOB='MANAGER'GROUPbyJOB