在应用系统开发初期,因为开发数据库数据比较少,对于查询sql语句,复杂试图的编写等体会不出sql语句各类写法的性能优劣,可是若是将应用系统提交实际应用后,随着数据库中数据的增长,系统的响应速度就成为目前系统须要解决的最主要问题之一。系统优化中一个很重要的方面就是sql语句的优化。对于海量数据,劣质sql语句和优质sql语句之间的速度差异能够达到上百倍,可见对于一个系统不是简单地能实现其功能就行,而是要写出高质量的sql语句,提升系统的可用性。程序员
Oracle的sql调优第一个复杂的主题,甚至须要长篇概论来介绍OracleSQL调优的细微差异。不过有一些基本的规则是每一个OracleDBA都须要听从的,这些规则能够改善他们系统的性能。sql
sql调优的目标是简单的:消除没必要要的大表全表搜索。没必要要的全表搜索致使大量没必要要的磁盘I/O,从而拖慢整个数据库的性能,对于没必要要的全表搜索来讲,最多见的调优方法是增长索引,能够在表中加入标准的B树索引,也能够加入位图索引和基于函数的索引。要决定是否消除一个全表搜索,你能够仔细检查索引搜索的I/O开销和全表搜索的开销,它们的开销和数据块的读取和可能的并行执行有关,并将二者做对比。数据库
另外,在全表搜索是一个最快的访问方法时,将小表的全表搜索放到缓存(内存)中,也是一个很是明智的选择。咱们会发现如今诞生了不少基于内存的数据库管理系统,将整个数据库置于内存之中,性能将获得质的飞跃。缓存
在多数状况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提升性能。可是,若是在sql语句的where子句中写的sql代码不合理,就会形成优化器删去索引而使用全表扫描,通常这种sql语句就是所谓的劣质sql语句。在编写sql语句时咱们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的sql语句。性能优化
不能用null作索引,任何包含null值的列都将不会被包含在索引中,即便索引有多列这样的状况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说某列存在空值,即便对该列创建索引也不会提升性能。任何在where子句中使用is null或者is nou null的语句优化器是不容许使用索引的。服务器
对于有联接的列,即便最后的联接列为一个静态值,优化器是不会使用索引的。来看个例子,假定有一个职工表(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';
一样拿上面的例子,目前的需求是这样的,要求在职工表中查询名字中包含Cliton的人,能够采用以下的查询sql语句:函数
select * from employee where last_name like '%Cliton%';
这里因为通配符(%)在搜寻词首出现,因此Oracle系统没法使用last_name的索引。在不少状况下可能没法避免这种状况,可是必定要内心有底,通配符这样使用会下降查询速度。然而当通配符出如今字符串其它位置时,优化器就能利用索引,在下面的查询中索引就获得了使用:
select * from employee where last_name like 'C%';
该语句查询全部姓名以C开头的,这彻底知足索引的要求,由于索引自己就是一个排序的列。
ORDER BY子句决定了Oracle如何将返回的查询结果排序。该子句对要排序的列没有什么特别的限制,也能够将函数加入到列中(象联接或者附加等)。任何在该子句的非索引项或者有计算表达式都将下降查询速度。
仔细检查order by子语句找出非索引项或者表达式,它们会下降性能。解决这个问题的办法就是重写order by子句以使用索引,也能够为所使用的列创建另一个索引,同时应绝对避免在order by子句中使用表达式。
咱们在查询时常常在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列使用索引,而第一种查询不能使用索引。
有时候会将一列和一系列值相比较,最简单的办法就是在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查询效率更高。
不等于操做符是永远不会用到索引的,由于对它的处理只会产生全表扫描。
推荐方案:用其它相同功能的操做运算符代替,如
a<>0 改成 a>0 or a<0, a<>'' 改成 a >''
WHERE子句中,若是索引列是函数的一部分,优化器将不使用索引而使用全表扫描
低效:
select ... from dept where SAL * 12 > 25000;
高效:
select ... from dept where SAL > 25000/12;
若是索引是创建在多个列上,只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引。这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。
当比较不一样数据类型的数据时,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会优先转换数值类型到字符类型。
某些select语句中的where子句不使用索引:
UNION在进行表连接后会筛选掉重复的记录,因此在表连接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录。最多见的是过程表与历史表UNION。如:
select * from A union select * from B;
这个sql在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,若是表数据量大的话可能会致使用磁盘进行排序。
推荐方案:采用UNION ALL操做符替代UNION,由于UNION ALL操做只是简单的将两个结果合并后就返回:
select * from A union all select * from B;
同一功能同一性能不一样写法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的执行频率。
当与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的值特别高,从而令其它任务的数据块由缓冲中移走。
带有 DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的sql语句回启动sql引擎执行耗费资源的排序(SORT)功能。DISTINCT须要一次排序操做,而其它的至少须要执行两次排序。一般,带有UNION,MINUS,INTERSECT的sql语句均可以用其它方式重写。若是你的数据库的sort_area_size调配的好,使用UNION,MINUS,INTERSECT也是能够考虑的,毕竟它们的可读性很强。
最高效的删除重复记录方法(因使用了ROWID)例子:
delete from emp E where E.ROWID > (select MIN(X.ROWID) from emp X where X.emp_no = E.emp_no);
当删除表中的记录时,在一般状况下,回滚段(rollback segments)用来存放能够被恢复的信息。若是你没有COMMIT事务,Oracle会将数据恢复到删除以前的状态(准确地说是恢复到执行删除以前的状态),而当运用TRUNCATE时,回滚段再也不存听任何可被恢复的信息。当命令运行后,数据不能被恢复,所以不多的资源被调用,执行时间也会很短(TRUNCATE只在清空全表适用,TRUNCATE是DDL而不是DML)。
3.SELECT 子句中避免使用 *
Oracle在解析的过程当中,会将 * 依次转换成全部的列名,这个工做是经过查询数据字典完成的,这意味着将耗费更多的时间。
避免使用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进行过滤。因而可知,要想过滤条件起到正确的做用,首先要明白这个条件应该在何时起做用,而后再决定放在哪里。
当在sql语句中链接多个表时,请使用表的别名并把别名前缀于每一个column上。这样一来,就能够减小解析的时间并减小那些由column歧义引发的语法错误。
在许多基于基础表的查询中,为了知足一个条件,每每须要对另外一个表进行联接。在这种状况下,使用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');
提交一个对多表信息(好比部门表和雇员表)进行查询的语句时,避免在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;
由于Oracle老是先解析sql语句,把小写的字母转换成大写的再执行
高效:
select * from emp where deptno >= 4;
低效:
select * from emp where deptno > 3;
二者的区别在于,前者DBMS将直接跳到第一个deptno等于4的记录,然后者将首先定位到deptno=3的记录而且向前扫描到第一个deptno大于3的记录。
提升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;