dmhql

导航

DM SQL优化 学习

3. SQL 优化规则

说起SQL优化,大家会像条件反射一样立即想到索引。但是,索引并非万能,它只是SQL优化的手段之一,并且作用有限,还有很多其他手段来提升SQL的性能。这些优化手段总结为如下几大类:共享SQL,避免排序,减少表的扫描,使用索引。

    SQL调优是个比较复杂,也很频繁的工作。实际上,绝大多数需要调优的SQL都是在编码的时候就出现了问题,如无绑定变量、重复扫描表、大量的排序、索引列计算、隐式转换等。维护阶段的SQL调优往往是救火,此时的系统已经慢的不能忍受了,由于不能修改应用等诸多限制,常常只能使用索引等有限的手段,优化效果不佳。

    但是,在开发阶段,我们可以使用各种手段来写出最优化的SQL,而不局限于索引。如果在开发阶段遵循某些优化规则,就能写出性能优良的SQL,必将减少后期繁琐的优化工作。防患于未然,何乐而不为?下面将详细介绍这些SQL优化规则。

3.1 共享SQL

3.1.1 统一编码风格

    SQL语句在执行之前需要解析,为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中。后面遇到相同的SQL语句,就不用再次解析,从而提高执行效率。然而,oracle判断SQL“相同”的条件非常苛刻,一是字符级别的比较,需要大小写、空格完全一样;另外一个对象相同,即是属于同一个schema的。 这里讨论的重点是字符级别的差异对SQL共享的影响。

    假如有4个开发人员,他们的编码风格各异,在应用中编写了下面的sql

A: select    *  from test

B: select    *  from TEST

C: select    *  from dbo.test

D: select    *     from test

 

虽然这4个sql功能完全一样,但oracle会认为它们是不相同的语句,都需要分别解析一次,SQL效率明显降低。

    因此,统一开发人员的编码风格很重要,如关键字大小写,表名、字段名大小写,表名是否带schema,空格个数等等。除了代码美观之外,这些更是保证SQL共享的关键之一。

3.1.2 使用绑定变量

在应用中使用绑定变量,可减少SQL的硬解析,这也是SQL共享的关键之一。

应尽量绑定变量:

select * from test where x = :id   --ORACLE

select * from test where x = ?    --DM

 

 

避免硬编码:

select * from test where x=1

select * from test where x=2

 ...

 

3.2 避免排序

3.2.1 避免排序

常见的排序操作有:

(1)order by,使用索引避免排序

(2)distinct

(3)union,尽可能用union all代替

(4)minus

(5)group by

3.2.2 用EXISTS替换DISTINCT

低效:

SELECT DISTINCT DEPT_NO,DEPT_NAME

FROM DEPT D,EMP E

WHERE D.DEPT_NO = E.DEPT_NO

 

 

高效:

SELECT DEPT_NO,DEPT_NAME

FROM DEPT D,EMP E

WHERE EXISTS ( SELECT ‘X’ FROM EMP E WHERE   E.DEPT_NO = D.DEPT_NO);

 

3.3 减少表的扫描

3.3.1 避免使用 *

在SELECT中引用 ‘*’ 的确很方便。不幸的是,这是一个非常低效的方法。 实际上,ORACLE在解析的过程中, 会将‘*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。也许用户只需要几个列而已,但是‘*’会返回所有的列数据到客户端,浪费宝贵的网络、存储资源。

3.3.2 减少表的重复扫描

减少表的扫描次数总是有效的,想尽各种办法减少表的扫描吧。

低效:扫描了2次EMP_CATEGORIES

UPDATE EMP

      SET EMP_CAT = (SELECT MAX(CATEGORY) FROM EMP_CATEGORIES),

          SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES)

WHERE EMP_DEPT = 0020;

 

 

高效:只扫描了一次EMP_CATEGORIES

UPDATE EMP

      SET (EMP_CAT, SAL_RANGE)

         = (SELECT MAX(CATEGORY) , MAX(SAL_RANGE) FROM EMP_CATEGORIES)

WHERE EMP_DEPT = 0020;

 

 

再来个真实应用中的例子(SQL做了简化):

低效:tmpBalance表被扫描了3次

select

          case when char_length(T.SUBJECTCODE)=4 then 0 else 1 end LEAFDATA  ,

          substr(T.SUBJECTCODE, 1, 4) SUBJECTCODE  ,

          T.SUBJECTNAME SUBJECTNAME                                                                                                                                                             then 1 else 0 end HASYE

from tmpBalance T where char_length(T.SUBJECTCODE)>=4

union all

select

          case when char_length(T.SUBJECTCODE)=6 then 0 else 1 end LEAFDATA  ,

          substr(T.SUBJECTCODE, 1, 6) SUBJECTCODE  ,

          T.SUBJECTNAME SUBJECTNAME                                                                                                                                                             then 1 else 0 end HASYE

from tmpBalance T where   char_length(T.SUBJECTCODE)>=6

union all

select

          case when char_length(T.SUBJECTCODE)=8 then 0 else 1 end LEAFDATA  ,

          substr(T.SUBJECTCODE, 1, 8) SUBJECTCODE  ,

          T.SUBJECTNAME SUBJECTNAME                                                                                                                                                             then   1 else 0 end HASYE

from tmpBalance T where   char_length(T.SUBJECTCODE)>=8

 

 

高效:tmpBalance只被扫描了一次,实际项目中此种写法效率比上种快3倍以上。

SELECT

          CASE WHEN char_length(T.SUBJECTCODE)=2*(r+1) THEN 0 ELSE 1 END   LEAFDATA,

          SUBSTR(T.SUBJECTCODE,1,2*(r+1)) SUBJECTCODE,

          T.SUBJECTNAME SUBJECTNAME

FROM tmpBalance T , (select level r from   dual connect by level <=3) seq

WHERE   char_length(T.SUBJECTCODE)>=2*(r+1)

 

 

3.3.3 使用表别名

当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间,并减少那些由Column歧义引起的语法错误。

3.3.4 用EXISTS替代IN

在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率。

低效:in子句需要找到所有满足条件的记录才会返回

SELECT * FROM EMP

WHERE  EMPNO > 0

AND DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE   LOC = ‘MELB’)

 

 

高效:exists子句只要找到一条符合条件的记录就马上返回

SELECT * FROM EMP

WHERE EMPNO > 0

AND EXISTS (SELECT ‘X’ FROM DEPT WHERE   DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’)

 

 

3.3.5 用NOT EXISTS替代NOT IN

低效:

SELECT … FROM EMP

WHERE DEPT_NO NOT IN (SELECT DEPT_NO FROM   DEPTWHERE DEPT_CAT=’A’);

 

 

高效:

SELECT ….FROM EMP E

WHERE NOT EXISTS (SELECT ‘X’  FROM DEPT D WHERE D.DEPT_NO = E.DEPT_NO  AND DEPT_CAT = ‘A’);

 

3.4 使用索引

3.4.1 用表连接替换EXISTS

通常来说 ,采用表连接的方式比EXISTS更有效率。

低效:

SELECT ENAME FROM EMP E

WHERE EXISTS (SELECT ‘X’ FROM DEPT WHERE   DEPT_NO = E.DEPT_NO  AND DEPT_CAT =   ‘A’);

 

 

高效:

SELECT ENAME

 FROM DEPT D,EMP E

WHERE E.DEPT_NO = D.DEPT_NO  AND DEPT_CAT = ‘A’ ;

 

 

3.4.2 避免索引列运算

对where条件中的索引列进行计算,或使用了函数,会导致该列无法走索引扫描。

无法使用索引:

SELECT    *  FROM service_promotion

WHERE TO_CHAR(gmt_modified,’yyyy-mm-dd’)   = ‘20001-09-01’;

 

 

可以使用索引:

SELECT * FROM service_promotion

WHERE gmt_modified >=   TO_DATE(‘2001-9-01’,’yyyy-mm-dd’)

AND gmt_modified <   TO_DATE(‘2001-9-02’,’yyyy-mm-dd’);

 

 

3.4.3 用UNION替换OR

通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果。 对索引列使用OR将造成全表扫描。注意, 以上规则只针对多个索引列有效。在下面的例子中, LOC_ID 和REGION上都建有索引。

低效:

SELECT LOC_ID , LOC_DESC ,REGION

FROM LOCATION  WHERE LOC_ID = 10 OR REGION = “MELBOURNE”

 

 

高效:

SELECT LOC_ID , LOC_DESC ,REGION

FROM LOCATION  WHERE LOC_ID = 10

UNION

SELECT LOC_ID ,LOC_DESC ,REGION

FROM LOCATION  WHERE REGION = “MELBOURNE”

 

 

3.4.4 用in替换or

低效:

SELECT…

FROM LOCATION

WHERE LOC_ID = 10  OR    LOC_ID = 20  OR  LOC_ID = 30

 

 

高效:

SELECT…

FROM LOCATION

WHERE LOC_IN IN (10,20,30)

 

 

3.4.5 避免IS NULL和IS NOT NULL

设计表时应注意,尽量将索引列设计为非空,用默认值代替null。因为在索引列上使用is null 和is not null操作将不会走索引。

低效: 不会走索引

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE IS NOT NULL;

 

 

高效: 走索引

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE >=0;

 

 

3.4.6 避免<>、not in、not like

<>、not in、not like等操作永远不会走索引。

3.4.7 小心like

只有前置like才能走索引,如 like 'abc%',其他如 like '%abc%'、'%abc'是不会走索引的。

3.4.8 避免隐式转换

当where条件中,做比较操作的两边数据类型不一致时,数据库会自动进行类型转换,并且总是把char类型的一方转换为number类型。若索引列发生了隐式转换,则无法走索引(这等同于在索引列上使用了函数)。

drop table tmp;

create table tmp(c1 int,c2 varchar(10));

insert into tmp select level c1,level c2   from dual

connect by level<=1000;

 

create index idx_c1 on tmp(c1);

create index idx_c2 on tmp(c2);

 

select * from tmp where c1=1;

--执行计划,能走索引

1     #NSET2: [0, 1, 22]

2       #PRJT2: [0, 1, 22]; exp_num(3), is_atom(FALSE)

3         #BLKUP2: [0, 1, 22]; IDX_C1(TMP)

4           #SSEK2: [0, 1, 22]; scan_type(ASC), IDX_C1(TMP), scan_range[1,1]

select * from tmp where c1='1';

--执行计划,能走索引,只是常量值发生隐式转换(下面标红处)

1     #NSET2: [0, 1, 22]

2       #PRJT2: [0, 1, 22]; exp_num(3), is_atom(FALSE)

3         #BLKUP2: [0, 1, 22]; IDX_C1(TMP)

4           #SSEK2: [0, 1, 22]; scan_type(ASC), IDX_C1(TMP), scan_range[exp_cast(1),exp_cast(1)]

select * from tmp where c2=1;

--执行计划,C2列发生数据类型转换(下面标红处),走全表扫描

1     #NSET2: [0, 1, 22]

2       #PRJT2: [0, 1, 22]; exp_num(3), is_atom(FALSE)

3         #SLCT2: [0, 1, 22]; exp_cast(TMP.C2) =   var1

4           #CSCN2: [0, 1000, 22]; INDEX33559060(TMP)

select * from tmp where c2='1';

--执行计划,未发生数据类型转换,能走索引

1     #NSET2: [0, 1, 22]

2       #PRJT2: [0, 1, 22]; exp_num(3), is_atom(FALSE)

3         #BLKUP2: [0, 1, 22]; IDX_C2(TMP)

4           #SSEK2: [0, 1, 22]; scan_type(ASC), IDX_C2(TMP), scan_range[1,1]

 

 

posted on 2020-06-23 18:27  dmhql  阅读(366)  评论(0编辑  收藏  举报