Oracle数据库的sql语句性能优化

  在应用系统开发初期,因为开发数据库数据比较少,对于查询sql语句,复杂试图的编写等体会不出sql语句各类写法的性能优劣,可是若是将应用系统提交实际应用后,随着数据库中数据的增长,系统的响应速度就成为目前系统须要解决的最主要问题之一。系统优化中一个很重要的方面就是sql语句的优化。对于海量数据,劣质sql语句和优质sql语句之间的速度差异能够达到上百倍,可见对于一个系统不是简单地能实现其功能就行,而是要写出高质量的sql语句,提升系统的可用性。程序员

  Oracle的sql调优第一个复杂的主题,甚至须要长篇概论来介绍OracleSQL调优的细微差异。不过有一些基本的规则是每一个OracleDBA都须要听从的,这些规则能够改善他们系统的性能。sql

  sql调优的目标是简单的:消除没必要要的大表全表搜索。没必要要的全表搜索致使大量没必要要的磁盘I/O,从而拖慢整个数据库的性能,对于没必要要的全表搜索来讲,最多见的调优方法是增长索引,能够在表中加入标准的B树索引,也能够加入位图索引和基于函数的索引。要决定是否消除一个全表搜索,你能够仔细检查索引搜索的I/O开销和全表搜索的开销,它们的开销和数据块的读取和可能的并行执行有关,并将二者做对比。数据库

  另外,在全表搜索是一个最快的访问方法时,将小表的全表搜索放到缓存(内存)中,也是一个很是明智的选择。咱们会发现如今诞生了不少基于内存的数据库管理系统,将整个数据库置于内存之中,性能将获得质的飞跃。缓存

1、与索引相关的性能优化

  在多数状况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提升性能。可是,若是在sql语句的where子句中写的sql代码不合理,就会形成优化器删去索引而使用全表扫描,通常这种sql语句就是所谓的劣质sql语句。在编写sql语句时咱们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的sql语句。性能优化

1.IS NULL 与 IS NOT NULL

  不能用null作索引,任何包含null值的列都将不会被包含在索引中,即便索引有多列这样的状况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说某列存在空值,即便对该列创建索引也不会提升性能。任何在where子句中使用is null或者is nou null的语句优化器是不容许使用索引的。服务器

2.联接列

  对于有联接的列,即便最后的联接列为一个静态值,优化器是不会使用索引的。来看个例子,假定有一个职工表(employee),对于一个职工的姓和名分红两列存放(FIRST_NAME和LAST_NAME),如今要查询一个叫Beill Cliton的职工。session

  下面是一个采用联接查询的sql语句:多线程

select * from employee where first_name ||''|| last_name = 'Beill Cliton';

  上面这条语句彻底能够查询出是否有Beill Cliton这个员工,可是这里须要注意,系统优化器对基于LAST_NAME建立的索引没有使用,当采用下面这种sql语句的编写,Oracle系统就能够采用基于LAST_NAME建立的索引:oracle

select * from employee where first_name = 'Beill' and last_name = 'Cliton';

3.带通配符(%)的like语句

  一样拿上面的例子,目前的需求是这样的,要求在职工表中查询名字中包含Cliton的人,能够采用以下的查询sql语句:函数

select * from employee where last_name like '%Cliton%';

  这里因为通配符(%)在搜寻词首出现,因此Oracle系统没法使用last_name的索引。在不少状况下可能没法避免这种状况,可是必定要内心有底,通配符这样使用会下降查询速度。然而当通配符出如今字符串其它位置时,优化器就能利用索引,在下面的查询中索引就获得了使用:

select * from employee where last_name like 'C%';

  该语句查询全部姓名以C开头的,这彻底知足索引的要求,由于索引自己就是一个排序的列。

4.ORDER BY 子句

  ORDER BY子句决定了Oracle如何将返回的查询结果排序。该子句对要排序的列没有什么特别的限制,也能够将函数加入到列中(象联接或者附加等)。任何在该子句的非索引项或者有计算表达式都将下降查询速度。

  仔细检查order by子语句找出非索引项或者表达式,它们会下降性能。解决这个问题的办法就是重写order by子句以使用索引,也能够为所使用的列创建另一个索引,同时应绝对避免在order by子句中使用表达式。

5.NOT 关键字

  咱们在查询时常常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算取反。下面是一个NOT子句的例子:

... where not(status = 'VALID');

  若是要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即便不在查询where子句中加入NOT关键字,NOT仍在运算符中,见下例:

... where status <> 'VALID';

  再看下面这个例子:

select * from employee where salary <> 3000;

  对这个查询,能够改写为不使用NOT:

select * from employee where salary < 3000 or salary > 3000;

  虽然这两种查询的结果是同样的,可是第二种查询方案会比第一种查询方案更快些。第二种查询容许Oralce对salary列使用索引,而第一种查询不能使用索引。

6.IN 和 EXISTS

  有时候会将一列和一系列值相比较,最简单的办法就是在where子句中使用子查询。在where子句中可使用两种格式的子查询。

  第一种格式是使用IN操做符:

... where column in (select * from ... where ...);

  第二种格式是使用EXISTS操做符:

... where exists (select 'X' from ... where ...);

  相信绝大多数人会使用第一种格式,由于它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle中能够几乎将全部的IN操做符子查询改写为使用EXISTS的子查询。

  第二种格式中,子查询以“select 'X'”开始,运用EXISTS子句,无论子查询从表中抽取什么数据,它只查看where子句,这样优化器就没必要遍历整个表而仅根据索引就可完成工做(这里假定在where语句中使用的列存在索引)。相对于IN子句来讲,EXISTS使用相连子查询构造起来要比IN子查询困难一些。

  经过使用EXISTS,Oracle系统会首先检查主查询,而后运行子查询直到它找到第一个匹配项,这就节省了时间。Oralce系统在执行IN子查询时,首先执行子查询,并将得到的结果列表放在一个加了索引的临时表中。在执行子查询以前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中之后再执行主查询。这也就是使用EXISTS比使用IN一般查询速度快的缘由。

  同时应尽量使用NOT EXISTS来代替NOT IN,尽管两者都使用了NOT(不能使用索引而下降速度),NOT EXISTS要比NOT IN查询效率更高。

7.<> 不等于符号

  不等于操做符是永远不会用到索引的,由于对它的处理只会产生全表扫描。

  推荐方案:用其它相同功能的操做运算符代替,如

  a<>0 改成 a>0 or a<0,  a<>'' 改成 a >''

8.避免在索引列上使用计算

  WHERE子句中,若是索引列是函数的一部分,优化器将不使用索引而使用全表扫描

  低效:

select ... from dept where SAL * 12 > 25000;

  高效:

select ... from dept where SAL > 25000/12;

9.老是使用索引的第一个列

  若是索引是创建在多个列上,只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引。这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。

10.避免改变索引列的类型

  当比较不一样数据类型的数据时,Oralce自动对列进行简单的类型转换。

  假设empno是一个数值类型的索引列:

select ... from emp where empno = '123';

  实际上,通过了Oracle类型转换,语句转化为:

select ... from emp where empno = TO_NUMBER('123');

  幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变。

  如今,假设emp_type是一个字符类型的索引列:

select ... from emp where emp_type = 123;

  这个语句被Oracle转换为:

select ... from emp where TO_NUMBER(emp_type) = 123;

  由于内部发生的类型转换,这个索引将不会被用到。为了不Oracle对sql进行隐式的类型转换,最好把类型转换用显示表现出来。注意当字符和数值比较时,Oracle会优先转换数值类型到字符类型。

11.须要小心的 WHERE 子句

  某些select语句中的where子句不使用索引:

  • '!='将不使用索引,索引只能告诉你什么存在于表中,而不能告诉你什么不存在于表中
  • '||'是字符链接函数,就像其它函数那样,停用了索引
  • '+'是数学函数,就像其它数学函数那样,停用了索引
  • 相同的索引列不能互相比较,这将会启动全表扫描

12.其它一些规则

  • 若是检索数据量超过30%的表中记录数,使用索引将没有显著的效率提升
  • 在特定状况下,使用索引也许会比全盘扫描慢,但这是同一个数量级上的区别。而一般状况下,使用索引比全表扫描要快几倍乃至几千倍
  • 避免在索引列上使用IS NULL 和IS NOT NULL
  • 避免在索引列上使用NOT
  • 用EXISTS替代IN、用NOT EXISTS替代NOT IN
  • 经过内部函数提升sql效率
  • 选择最有效的表名顺序:Oracle的解析器按照从右到左的顺序处理from子句中的表名,from子句中写在最后的表(基础表)将被最早处理,在from子句中包含多个表的状况下,你必须选择记录条数最少的表做为基础表。若是有三个以上的表链接查询,那就须要选择交叉表做为基础表,交叉表是指被其它表所引用的表
  • WHERE子句中的链接顺序:Oracle采用自下而上的顺序解析where子句,根据这个原理,表之间的链接必须写在其它where条件以前,那些能够过滤掉最大数量记录的条件必须写在where子句的末尾

2、与内存相关的优化

1.UNION 操做符

  UNION在进行表连接后会筛选掉重复的记录,因此在表连接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录。最多见的是过程表与历史表UNION。如:

select * from A union select * from B;

  这个sql在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,若是表数据量大的话可能会致使用磁盘进行排序。

  推荐方案:采用UNION ALL操做符替代UNION,由于UNION ALL操做只是简单的将两个结果合并后就返回:

select * from A union all select * from B;

2.SQL书写的影响

  同一功能同一性能不一样写法sql的影响

  如,一个sql在A程序员写的为:

select * from employee;

  B程序员写为:

select * from scott.employee; (带表全部者的前缀)

  C程序员写为:

select * from EMPLOYEE; (大写表名)

  D程序员写为:

select *   from employee; (中间多了空格)

  以上四个sql在Oracle分析整理以后产生的结果及执行的时间是同样的,可是从Oracle共享内存SGA的原理,能够得出Oracle对每一个sql都会对其进行一次分析,而且占用共享内存,若是将sql的字符串及格式写得彻底相同则Oracle只会分析一次,共享内存也只会留下一次的分析结果,这不只能够减小分析sql的时间,并且也能够减小共享内存重复的信息,oracle也能够准确统计sql的执行频率。

3.避免在磁盘中排序

  当与Oracle创建起一个session时,在内存中就会为该session分配一个私有的排序区域。若是该链接是一个专用的链接(dedicated connection),那么就会根据init.ora中sort_area_size参数的大小在内存中分配一个Program Global Area(PGA)。若是链接是经过多线程服务器创建的,那么排序的空间就在large_pool中分配。不幸的是,对于全部的session,用做排序的内存量都必须是同样的,咱们不能为须要更大排序的操做分配额外的排序区域。所以,设计者必须作出一个平衡,在分配足够的排序区域以免发生大的排序任务时出现磁盘排序(disksorts)的同时,对于那些并不须要进行很大排序的任务,就会出现一些浪费。固然,当排序的空间需求超出了sort_area_size的大小时,这时将会在TEMP表空间中分页进行磁盘排序。磁盘排序要比内存排序大概慢14000倍。

  上面咱们已经提到,私有排序区域的大小是由init.ora中的sort_area_size参数决定的。每一个排序所占用的大小由init.ora中的sort_area_size参数决定。当排序不能在分配的空间中完成时,就会使用磁盘排序的方式,即在Oracle实例中的临时表空间中进行。

  磁盘排序的开销是很大的,有几个方面的缘由。首先,和内存排序相比较,它们特别慢;并且,磁盘排序会消耗临时表空间中的资源。Oracle还必须分配缓冲池块来保持临时表空间中的块。不管何时,内存排序都比磁盘排序好,磁盘排序将会令任务变慢,而且会影响Oracle实例的当前任务的执行。还有,过多的磁盘排序将会令freebufferwaits的值特别高,从而令其它任务的数据块由缓冲中移走。

4.避免使用耗费资源的操做

  带有 DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的sql语句回启动sql引擎执行耗费资源的排序(SORT)功能。DISTINCT须要一次排序操做,而其它的至少须要执行两次排序。一般,带有UNION,MINUS,INTERSECT的sql语句均可以用其它方式重写。若是你的数据库的sort_area_size调配的好,使用UNION,MINUS,INTERSECT也是能够考虑的,毕竟它们的可读性很强。

3、其它性能优化相关技巧

1.删除重复记录

  最高效的删除重复记录方法(因使用了ROWID)例子:

delete from emp E where E.ROWID > (select MIN(X.ROWID) from emp X where X.emp_no = E.emp_no);

2.用 TRUNCATE 替代 DELETE

  当删除表中的记录时,在一般状况下,回滚段(rollback segments)用来存放能够被恢复的信息。若是你没有COMMIT事务,Oracle会将数据恢复到删除以前的状态(准确地说是恢复到执行删除以前的状态),而当运用TRUNCATE时,回滚段再也不存听任何可被恢复的信息。当命令运行后,数据不能被恢复,所以不多的资源被调用,执行时间也会很短(TRUNCATE只在清空全表适用,TRUNCATE是DDL而不是DML)。

3.SELECT 子句中避免使用 *

  Oracle在解析的过程当中,会将 * 依次转换成全部的列名,这个工做是经过查询数据字典完成的,这意味着将耗费更多的时间。

4.用 WHERE 子句替换 HAVING 子句

  避免使用having子句,having只会在检索出全部记录以后才对结果集进行过滤,这个处理须要排序、总计等操做。若是能经过where子句限制记录的数目,那就能减小这方面的开销。sql语句中on、where、having这三个均可以加条件的子句中,on是最早执行,where次之,having最后,由于on是先把不符合条件的记录过滤后才进行统计,它就能够减小中间运算要处理的数据,按理说应该是速度最快的,where也应该比having快点的,由于它过滤数据后才进行sum,在两个表链接时采用on,因此在一个表的时候,就剩下where跟having比较了。在单表查询统计的状况下,若是要过滤的条件没有涉及到要计算字段,那它们的结果是同样的,只是where可使用rushmore技术,而having就不能,在速度上后者要慢若是要涉及到计算的字段,就表示在没计算以前,这个字段的值是不肯定的,where的做用时间是在计算以前就完成的,而having就是在计算以后才起做用的,因此在这种状况下,二者的结果会不一样。在多表联接查询时,on比where更早起做用,系统首先根据各个表之间的链接条件,把多个表合成一个临时表后,再由where进行过滤,而后再计算,计算完成后再由having进行过滤。因而可知,要想过滤条件起到正确的做用,首先要明白这个条件应该在何时起做用,而后再决定放在哪里。

5.使用表的别名(Alias)

  当在sql语句中链接多个表时,请使用表的别名并把别名前缀于每一个column上。这样一来,就能够减小解析的时间并减小那些由column歧义引发的语法错误。

6.用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN

  在许多基于基础表的查询中,为了知足一个条件,每每须要对另外一个表进行联接。在这种状况下,使用EXISTS(或NOT EXISTS)一般将提升查询的效率。在子查询中NOT IN子句将执行一个内部的排序和合并。不管在哪一种状况下,NOT IN都是最低效的(由于它对子查询中的表执行了一个全表遍历)。为了不使用NOT IN,咱们能够把它改写成外联接(OUTER JOIN)或NOT EXISTS。

  高效:

select * from emp where empno > 0 and EXISTS (select 'X' from dept where dept.deptno = emp.deptno and loc = 'MELB');

  低效:

select * from emp where empno > 0 and deptno IN (select deptno from dept where loc = 'MELB');

7.用 EXISTS 替换 DISTINCT

  提交一个对多表信息(好比部门表和雇员表)进行查询的语句时,避免在select子句中使用distinct。通常能够考虑用EXISTS替换,EXISTS使查询更为迅速,由于RDBMS核心模块将在子查询的条件一但知足后,马上返回结果。

  高效:

select dept_no, dept_name from dept D where EXISTS (select 'X' from emp E where E.deptno = D.deptno);

  低效:

select DESTINCT dept_no, dept_name from dept D, emp E where D.deptno = E.deptno;

8.SQL语句使用大写

  由于Oracle老是先解析sql语句,把小写的字母转换成大写的再执行

9.用 >= 替代 >

  高效:

select * from emp where deptno >= 4;

  低效:

select * from emp where deptno > 3;

  二者的区别在于,前者DBMS将直接跳到第一个deptno等于4的记录,然后者将首先定位到deptno=3的记录而且向前扫描到第一个deptno大于3的记录。

10.优化 GROUP BY

  提升group by语句的效率,能够经过将不须要的记录在group by以前过滤掉。下面两个查询返回相同结果,但第二个就明显快了许多。

  低效:

select job, AVG(SAL) from emp group by job having job = 'PRESIDENT' or job = 'MANAGER';

  高效:

select job, AVG(SAL) from emp where job = 'PRESIDENT' or job = 'MANAGER' group by job;
相关文章
相关标签/搜索