天天看点

常用的SQL优化技巧

从使用索引来考虑SQL语句

  • 避免索引列上的函数,如SUBSTR/UPPER/NVL/TO_CHAR/TO_DATE/TRUNC
  • 避免索引列上的计算公式:如果索引列上使用了计算公式,则索引不能使用,可以通过更改计算公式来避免

    不使用索引:Select e.Ename From Emp e Where e.Sal * 1.1 > 900

    使用索引:Select e.Ename From Emp e Where e.Sal > 900 / 1.1

    低效: Select … FROM DEPARTMENT Where DEPT_CODE IS NOT NULL;

    高效: Select … FROM DEPARTMENT Where DEPT_CODE >=0;

  • 避免使用not in,使用not exists代替

    Select e.Ename From Emp e Where e.Deptno Not In (Select d.Deptno From Dept d)

    Select e.Ename From Emp e Where e.Deptno Not Exists (Select ‘x’ From Dept d Where d.Deptno = e.Deptno)

  • LIKE的使用: LIKE用于模糊检索,LIKE检索的样式有三种:前匹配(XX%)、中间匹配(X%X)、后匹配(%XX),对于前匹配可以使用索引,而使用中间匹配和后匹配,都不能使用索引。因此除非必要,否则应尽量避免使用中间匹配和后匹配
  • 复合索引的使用:要使用复合索引,where语句中必须包括复合索引中的所有列或前几个列。如果复合索引的第一个列不在where语句中则不应该使用该复合索引

从减少系统负荷来考虑SQL语句

  • 使用表别名:通过对表附加别名,SQL编译时可以明确列的来源表,从而使得SQL的编译时间缩短
  • ROWNUM的使用:ROWNUM可以限制检索数据的数量,如果为了判断对象是否存在,使用ROWNUM=1是非常有效的
  • UNION和UNION ALL:如果确定联合中不会出现重复数据的话,必须设定UNION ALL来取消自动分类以提高检索速度
  • 替代DISTINCT:尽量少使用DISTINCT,使用DISTINCT将引起内部排序处理,如果可以的话,尽量使用EXISTS、NOT EXISTS或子查询来避免

    Select Distinct d.Deptno, d.Dname From Dept d, Emp e Where d.Deptno = e.DeptnoSelect d.Deptno, d.Dname From Dept d Where Exists (Select ‘x’ From Emp e Where d.Deptno = e.Deptno)

  • 避免视图滥用:如果查询只检索基表的几个字段,应避免直接使用视图,造成不必要的数据块检索
  • 编写可再利用性的SQL语句:需要动态组合条件时,应避免直接将变量值组合到条件中去,而应该使用变量绑定,从而提高SQL语句的再利用性

Select子句中避免使用*

当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 * 是一个方便的方法.不幸的是,这是一个非常低效的方法. 实际上,ORACLE在解析的过程中, 会将 * 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间.

使用decode函数来减少处理时间

使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.

例如:

select count() from pts_work_centers t where t.organization_id = 85

and t.work_center_name like ‘%氧化%’;

select count() from pts_work_centers t where t.organization_id = 86

and t.work_center_name like ‘%氧化%’;

用decode可以写成:

select count(decode(t.organization_id,85,‘X’)),

count(decode(t.organization_id,86,‘X’))

from pts_work_centers t

where t.work_center_name like ‘%氧化%’;

用Where子句替换HAVING子句

避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销.

低效:

select t.organization_id,count(*)

from pts_work_centers t

group by t.organization_id

having t.organization_id not in(85,86);

高效:

select t.organization_id,count(*)

from pts_work_centers t

where t.organization_id not in(85,86)

group by t.organization_id

HAVING 中的条件一般用于对一些集合函数的比较,如COUNT() 等等. 除此而外,一般的条件应该写在WHERE子句中

需要当心的Where子句:

某些Select 语句中的Where子句不使用索引. 这里有一些例子.

在下面的例子里

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

利用空值不会出现在索引的原则来提高SQL性能

假如某个字段,对于大多数记录而言是一个固定值,只有少数记录是另外其他值,而我们的程序中通常要查询这些少数据记录。

例如,库存事务表的‘成本核算’字段,大多数记录肯定是已核算成本,只有少部分记录是未核算成本,而成本管理器就仅仅想查询这些未核算成本的记录,如果我们设计‘已核算’状态的值为‘Y’,‘未核算’状态为‘N’,那这个查询会消耗巨大。如果我们设计‘已核算’状态的值为‘’,‘未核算’状态为‘N’,那么查询速度会非常快: select transaction_id from mmt where cost_flag = ‘N’

避免改变索引列的类型

避免ORACLE对你的SQL进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE会优先转换数值类型到字符类型.

假设 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

因为内部发生的类型转换, 这个索引将不会被用到!

识别’低效执行’的SQL语句

用下列SQL找出低效SQL:

SELECT EXECUTIONS , DISK_READS, BUFFER_GETS,

ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio,

ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run,

SQL_TEXT

FROM V$SQLAREA

WHERE EXECUTIONS>0

AND BUFFER_GETS > 0

AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8

ORDER BY 4 DESC;

select * from (  
    select * from V$SQLSTATS        
    -- 最耗时的 SQL  
    -- ELAPSED_TIME 指的是总耗时(毫秒),平均耗时 = ELAPSED_TIME/EXECUTIONS  
    -- order by ELAPSED_TIME DESC  
      
    -- 查询执行次数最多的 SQL  
    -- order by EXECUTIONS DESC  
      
    -- 读硬盘最多的 SQL  
    -- order by DISK_READS DESC  
      
    -- 最费 CPU 的 SQL  
    -- order by BUFFER_GETS DESC  

) where rownum <=50; 
           

查找前十条性能差的sql

SELECT * FROM

(

SELECT PARSING_USER_ID

EXECUTIONS,

SORTS,

COMMAND_TYPE,

DISK_READS,

sql_text

FROM v$sqlarea

ORDER BY disk_reads DESC

) WHERE ROWNUM<10 ;