zoukankan      html  css  js  c++  java
  • 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]

  • 相关阅读:
    Activity的活动Menu和上下文Menu使用示例
    drawable之scale and rotate代码示例
    密度无关和缩放比例无关定义
    [前端优化]使用Combres合并对js、css文件的请求
    不同版本的SQL Server之间数据导出导入的方法及性能比较
    SqlBulkCopy加了事务真的会变快吗?
    ASP.NET Web Forms 4.5的新特性(二):针对HTML5的更新和Unobtrusive Validation
    MVC3缓存之一:使用页面缓存
    MVC3缓存之三:MVC3中的局部缓存(Partial Page)
    ASP.NET Web Forms 4.5的新特性(三):Model Binding
  • 原文地址:https://www.cnblogs.com/DmPOL/p/13183744.html
Copyright © 2011-2022 走看看