前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【DB笔试面试570】在Oracle中,SQL优化在写法上有哪些常用的方法?

【DB笔试面试570】在Oracle中,SQL优化在写法上有哪些常用的方法?

作者头像
小麦苗DBA宝典
发布2019-09-29 15:45:24
3.5K0
发布2019-09-29 15:45:24
举报

题目部分

在Oracle中,SQL优化在写法上有哪些常用的方法?

答案部分

一般在书写SQL时需要注意哪些问题,如何书写可以提高查询的效率呢?可以从以下几个方面去考虑:

(1)减少对数据库的访问次数。

当执行每条SQL语句时,Oracle在内部执行了许多工作:解析SQL语句,估算索引的利用率,绑定变量,读数据块等等。由此可见,减少访问数据库的次数,就能实际上减少Oracle的工作量。充分利用表索引,避免进行全表扫描;充分利用共享缓存机制,提高SQL工作效率;充分利用结构化编程方式,提高查询的复用能力。常用的方法为把对数据库的操作写成存储过程,然后应用程序通过调用存储过程,而不是直接使用SQL。

(2)减少对大表的扫描次数。可以利用WITH对SQL中多次扫描的表来进行修改。采用各种手段来避免全表扫描。

(3)SELECT子句中避免使用“*”,应该写出需要查询的字段。

当想在SELECT子句中列出所有的列时,可以使用“*”来返回所有的列,但这是一个非常低效的方法。实际上,Oracle在解析的过程中,会将“*”依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。不需要的字段尽量少查,多查的字段可能有行迁移或行链接(timesten还有行外存储问题)。少查LOB类型的字段可以减少I/O。

(4)尽量使用表的别名(ALIAS)。

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

(5)对于数据量较少、又有主键索引的情况,可以考虑将关联子查询或外连接的SQL修改为标量子查询。

(6)避免隐式类型转换(Implicit Type Conversion)。如果进行比较的两个值的数据类型不同,那么Oracle必须将其中一个值进行类型转换使其能够比较。这就是所谓的隐式类型转换。通常当开发人员将数字存储在字符列时会导致这种问题的产生。Oracle在运行时会在索引字符列使用TO_NUMBER函数强制转化字符类型为数值类型。由于添加函数到索引列所以导致索引不被使用。实际上,Oracle也只能这么做,类型转换是一个应用程序设计因素。由于转换是在每行都进行的,这会导致性能问题。一般情况下,当比较不同数据类型的数据时,Oracle自动地从复杂向简单的数据类型转换,该规则和MySQL中的隐式类型转换是一致的。所以,字符类型的字段值应该加上引号。例如,假设USER_NO是一个字符类型的索引列,则:

代码语言:javascript
复制
SELECT USER_NO,USER_NAME,ADDRESS FROM USER_FILES WHERE USER_NO = 109204421;

--这个语句在执行的时候被Oracle在内部自动的转换为:
SELECT USER_NO,USER_NAME,ADDRESS FROM USER_FILES WHERE TO_NUMBER(USER_NO) = 109204421;

--因为内部发生的类型转换,这个索引将不会被使用,所以正确的写法应该是:
SELECT USER_NO,USER_NAME,ADDRESS FROM USER_FILES WHERE USER_NO = '109204421';

--但是,在下面的SQL语句中,Oracle隐式地将字符串“03-MAR-97”转化为默认日期类型为“DD-MON-YY”的日期:
SELECT LAST_NAME FROM EMPLOYEES WHERE HIRE_DATE = '03-MAR-97';

Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117

-------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |     1 |    69 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMPLOYEES |     1 |    69 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("HIRE_DATE"='24-APR-06')

(7)避免使用耗费资源的操作,包括DISTINCT、UNION、MINUS、INTERSECT、ORDER BY、GROUP BY等。能用DISTINCT的就不用GROUP BY。能用UNION ALL就不要用UNION。

(8)用TRUNCATE替代DELETE。若要删除表中所有的数据,则可以用TRUNCATE替代DELETE。

(9)根据查询条件建立合适的索引,利用索引可以避免大表全表扫描(FULL TABLE SCAN)。

(10)合理使用临时表。

(11)避免写过于复杂的SQL,不一定非要一个SQL解决问题。将一个大的SQL改写为多个小的SQL来实现功能。条件允许的情况下可以使用批处理来完成。

(12)在不影响业务的前提下尽量减小事务的粒度。

(13)当使用基于规则的优化器(RBO)时,在多表连接查询的时候,记录数少的表应该放在右边。

(14)避免使用复杂的集合函数,像NOT IN等。通常,要避免在索引列上使用NOT,NOT会产生和在索引列上使用函数相同的影响。当Oracle遇到NOT操作符时,它就会停止使用索引转而执行全表扫描。很多时候用EXISTS和NOT EXISTS代替IN和NOT IN语句是一个好的选择。需要注意的是,在Oracle 11g之前,若NOT IN的列没有指定非空的话(注意:是主表和子表的列未同时有NOT NULL约束,或都未加IS NOT NULL限制),则NOT IN选择的是filter操作(如果指定了非空,那么会选择ANTI的反连接),但是从Oracle 11g开始有新的ANTI NA(NULL AWARE)优化,可以对子查询进行UNNEST,NOT IN和NOT EXISTS都选择的是ANTI的反连接,所以效率是一样的。在一般情况下,ANTI的反连接算法比filter更高效。对于未UNNEST的子查询,若选择了filter操作,则至少有两个子节点,执行计划还有个特点就是Predicate谓词部分有“:B1”这种类似绑定变量的内容,内部操作走类似Nested Loops操作。如果在Oracle 11g之前,遇到NOT IN无法UNNEST,那么可以将NOT IN部分的匹配条件均设为NOT NULL约束。若不添加NOT NULL约束,则需要两个条件均增加IS NOT NULL条件。当然也可以将NOT IN修改为NOT EXISTS。关于反连接的更多内容参考【3.2.5.10 什么是半连接、反连接和星型连接?】。

分别在Oracle 10g和Oracle 11g实验:

代码语言:javascript
复制
SELECT * FROM V$VERSION;
DROP TABLE EMP PURGE;
DROP TABLE DEPT PURGE;
CREATE TABLE EMP AS SELECT * FROM SCOTT.EMP;
CREATE TABLE DEPT AS SELECT * FROM SCOTT.DEPT;
SET TIMING ON
SET LINESIZE 1000
SET AUTOTRACE TRACEONLY
--写法1
SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP);
--写法2
SELECT * FROM DEPT WHERE NOT EXISTS (SELECT DEPTNO FROM EMP WHERE EMP.DEPTNO=DEPT.DEPTNO);
--写法3
SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP WHERE DEPTNO IS NOT NULL) AND DEPTNO IS NOT NULL;
--写法4
SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP) AND DEPTNO IS NOT NULL;
--写法5
SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP WHERE DEPTNO IS NOT NULL);

看一下详细执行计划:

代码语言:javascript
复制
SELECT * FROM V$VERSION;
DROP TABLE EMP PURGE;
DROP TABLE DEPT PURGE;
CREATE TABLE EMP AS SELECT * FROM SCOTT.EMP;
CREATE TABLE DEPT AS SELECT * FROM SCOTT.DEPT;
SET TIMING ON
SET LINESIZE 1000
SET AUTOTRACE TRACEONLY
--写法1
SELECT /*+optimizer_features_enable('10.2.0.5')*/ * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP);
SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP);

LHR@orclasm > SELECT /*+optimizer_features_enable('10.2.0.5')*/ * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP);

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 OPERATIONS     BOSTON


Execution Plan
----------------------------------------------------------
Plan hash value: 3547749009

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    30 |     5   (0)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| DEPT |     4 |   120 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMP  |    13 |   169 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( NOT EXISTS (SELECT 0 FROM "EMP" "EMP" WHERE
              LNNVL("DEPTNO"<>:B1)))
   3 - filter(LNNVL("DEPTNO"<>:B1))

Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
         15  recursive calls
          0  db block gets
         31  consistent gets
          0  physical reads
          0  redo size
        674  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

LHR@orclasm > SELECT * FROM DEPT WHERE DEPTNO NOT IN (SELECT DEPTNO FROM EMP);

    DEPTNO DNAME          LOC
---------- -------------- -------------
        40 OPERATIONS     BOSTON


Execution Plan
----------------------------------------------------------
Plan hash value: 2100826622

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |   172 |     7  (15)| 00:00:01 |
|*  1 |  HASH JOIN ANTI NA |      |     4 |   172 |     7  (15)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPT |     4 |   120 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMP  |    14 |   182 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("DEPTNO"="DEPTNO")

Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         14  consistent gets
          0  physical reads
          0  redo size
        674  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

针对上面的NOT IN子查询,如果子查询中的DEPTNO有NULL存在,那么整个查询都不会有结果,在Oracle 11g之前,如果主表和子表的DEPTNO未同时有NOT NULL约束,或都未加IS NOT NULL限制,那么Oracle会选择filter。从Oracle 11g开始有新的ANTI NA(NULL AWARE)优化,可以对子查询进行UNNEST,从而提高效率。对于未UNNEST的子查询,若选择了FILTER操作,则至少有两个子节点,执行计划还有个特点就是Predicate谓词部分有“:B1”这种类似绑定变量的内容,内部操作走类似Nested Loops操作。

如下所示:

代码语言:javascript
复制
LHR@orclasm > SELECT  /*+rule gather_plan_statistics*/  *
  2    FROM SCOTT.EMP
  3   WHERE NOT EXISTS (SELECT 0
  4            FROM SCOTT.DEPT
  5           WHERE DEPT.DNAME = 'SALES'
  6             AND DEPT.DEPTNO = EMP.DEPTNO)
  7     AND NOT EXISTS
  8   (SELECT 0 FROM SCOTT.BONUS WHERE BONUS.ENAME = EMP.ENAME);

     EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 1980-12-17 00:00:00        800                    20
      7566 JONES      MANAGER         7839 1981-04-02 00:00:00       2975                    20
      7782 CLARK      MANAGER         7839 1981-06-09 00:00:00       2450                    10
      7788 SCOTT      ANALYST         7566 1987-04-19 00:00:00       3000                    20
      7839 KING       PRESIDENT            1981-11-17 00:00:00       5000                    10
      7876 ADAMS      CLERK           7788 1987-05-23 00:00:00       1100                    20
      7902 FORD       ANALYST         7566 1981-12-03 00:00:00       3000                    20
      7934 MILLER     CLERK           7782 1982-01-23 00:00:00       1300                    10

8 rows selected.

LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ADVANCED ALLSTATS LAST PEEKED_BINDS'));


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID  b8w1s38hqtjkj, child number 0
-------------------------------------
SELECT  /*+rule gather_plan_statistics*/  *   FROM SCOTT.EMP  WHERE NOT
EXISTS (SELECT 0           FROM SCOTT.DEPT          WHERE DEPT.DNAME =
'SALES'            AND DEPT.DEPTNO = EMP.DEPTNO)    AND NOT EXISTS
(SELECT 0 FROM SCOTT.BONUS WHERE BONUS.ENAME = EMP.ENAME)

Plan hash value: 1445856646

----------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name    | Starts | A-Rows |   A-Time   | Buffers | 
----------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |         |      1 |      8 |00:00:00.01 |      14 | 
|*  1 |  FILTER                      |         |      1 |      8 |00:00:00.01 |      14 | 
|   2 |   TABLE ACCESS FULL          | EMP     |      1 |     14 |00:00:00.01 |       8 | 
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPT    |      3 |      1 |00:00:00.01 |       6 | 
|*  4 |    INDEX UNIQUE SCAN         | PK_DEPT |      3 |      3 |00:00:00.01 |       3 | 
|*  5 |   TABLE ACCESS FULL          | BONUS   |      8 |      0 |00:00:00.01 |       0 | 
-----------------------------------------------------------------------------------------  

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / EMP@SEL$1
   3 - SEL$2 / DEPT@SEL$2
   4 - SEL$2 / DEPT@SEL$2
   5 - SEL$3 / BONUS@SEL$3

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.3')
      DB_VERSION('11.2.0.3')
      RBO_OUTLINE
      OUTLINE_LEAF(@"SEL$2")
      OUTLINE_LEAF(@"SEL$3")
      OUTLINE_LEAF(@"SEL$1")
      FULL(@"SEL$1" "EMP"@"SEL$1")
      FULL(@"SEL$3" "BONUS"@"SEL$3")
      INDEX_RS_ASC(@"SEL$2" "DEPT"@"SEL$2" ("DEPT"."DEPTNO"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(( IS NULL AND  IS NULL))
   3 - filter("DEPT"."DNAME"='SALES')
   4 - access("DEPT"."DEPTNO"=:B1)
   5 - filter("BONUS"."ENAME"=:B1)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "EMP"."EMPNO"[NUMBER,22], "EMP"."ENAME"[VARCHAR2,10],
       "EMP"."JOB"[VARCHAR2,9], "EMP"."MGR"[NUMBER,22], "EMP"."HIREDATE"[DATE,7],
       "EMP"."SAL"[NUMBER,22], "EMP"."COMM"[NUMBER,22], "EMP"."DEPTNO"[NUMBER,22]
   2 - "EMP"."EMPNO"[NUMBER,22], "EMP"."ENAME"[VARCHAR2,10],
       "EMP"."JOB"[VARCHAR2,9], "EMP"."MGR"[NUMBER,22], "EMP"."HIREDATE"[DATE,7],
       "EMP"."SAL"[NUMBER,22], "EMP"."COMM"[NUMBER,22], "EMP"."DEPTNO"[NUMBER,22]
   4 - "DEPT".ROWID[ROWID,10]

Note
-----
   - rule based optimizer used (consider using cbo)


70 rows selected.


----------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name    | Starts | A-Rows |   A-Time   | Buffers | 
----------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |         |      1 |      8 |00:00:00.01 |      14 | 
|*  1 |  FILTER                      |         |      1 |      8 |00:00:00.01 |      14 | 
|   2 |   TABLE ACCESS FULL          | EMP     |      1 |     14 |00:00:00.01 |       8 | 
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPT    |      3 |      1 |00:00:00.01 |       6 | 
|*  4 |    INDEX UNIQUE SCAN         | PK_DEPT |      3 |      3 |00:00:00.01 |       3 | 
|*  5 |   TABLE ACCESS FULL          | BONUS   |      8 |      0 |00:00:00.01 |       0 | 
-----------------------------------------------------------------------------------------  

Predicate Information (identified by operation id): 
--------------------------------------------------- 
1 - filter(( IS NULL AND  IS NULL)) 
3 - filter("DEPT"."DNAME"='SALES') 
4 - access("DEPT"."DEPTNO"=:B1) 
5 - filter("BONUS"."ENAME"=:B1) 

该执行计划的执行顺序为:

① ID1有3个子节点ID2、ID3、ID5,由于ID2最小,故先执行ID2;

② ID2对EMP表进行全表扫描,将返回14行给ID1;

③ 在相关组合中ID2应当控制ID3和ID5的执行,由于Oracle此处对Distinct Value做了优化,所以ID3只执行了3次。

④ ID4执行3次,并返回3个RWOID到ID3;

⑤ ID3使用ID4返回的3个ROWID来访问数据表块,过滤“filter("DEPT"."DNAME"='SALES')”的数据,由于是NOT EXISTS,所以这导致ID1原来获得的14行排除6行的“"DEPT"."DNAME"='SALES'”,只剩下8行,这8行数据影响了ID5的执行次数,将执行8次,其中“filter("BONUS"."ENAME"=:B1)”过滤条件的“:B1”由ID1的8行数据提供,ID5没有返回数据,所以那8行没有减少ID1将8行彻底过滤的数据返回给客户端。

(15)尽量避免使用UNION关键词,可以根据情况修改为UNION ALL。

(16)在Oracle数据库里,IN和OR是等价的,优化器在处理带IN的目标SQL时会将其转换为带OR的等价SQL。例如,“DEPTNO IN (10,20)”和“DEPTNO=10 OR DEPTNO=20”是等价的。

(17)选择合适的谓词进行过滤。

(18)避免使用前置通配符(%)。在WHERE子句中,如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始,索引将不被采用。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其它位置时,优化器就能利用索引。若前置通配符实在无法取消,则可以从2个方面去考虑。①去重和去空。应该把表中的重复记录或者为空的记录全部去掉,这样可以大大减少结果集,因而提升性能,这里也体现了大表变小表的思想;②考虑建立文本索引。③做相关的转换,请参考【3.2.5.3 模糊查询可以使用索引吗?】。

(19)应尽量避免在WHERE子句中对索引字段进行函数、算术运算或其他表达式等操作,因为这样可能会使索引失效,查询时要尽可能将操作移至等号右边。见如下例子:

SELECT * FROM T1 WHERE SUBSTR(NAME,2,1)='L';

在以上SQL中,即使NAME字段建有唯一索引,该SQL语句也无法利用索引进行检索数据,而是走全表扫描的方式。一些常见的改写如下表所示:

原SQL语句

优化后SQL语句

SELECT * FROM T1 WHERE COL/2=100;

SELECT * FROM T1 WHERE COL=200;

SELECT * FROM T1 WHERE SUBSTR(CARD_NO,1,4)='5378';

SELECT * FROM T1 WHERE CARD_NO LIKE '5378%';

SELECT * FROM T1 WHERE TO_CHAR(CREATED,'YYYY') = '2011';

SELECT * FROM T1 WHERE CREATED >= TO_DATE('20110101','YYYYMMDD') AND CREATED < TO_DATE('20120101','YYYYMMDD');

SELECT * FROM T1 WHERE TRUNC(CREATED)=TRUNC(SYSDATE);

SELECT * FROM T1 WHERE CREATED >= TRUNC(SYSDATE) AND CREATED < TRUNC(SYSDATE+1);

SELECT * FROM T1 WHERE 'X'||COL2>'X5400021452';

SELECT * FROM T1 WHERE COL2>'5400021452';

SELECT * FROM T1 WHERE COL||COL2='5400250000';(在该SQL中,COL和COL2列长度固定)

SELECT * FROM T1 WHERE COL='5400' AND COL2='250000';

SELECT * FROM T1 WHERE TO_CHAR(CREATED,'YYYY') = TO_CHAR(ADD_MONTHS(SYSDATE, -12),'YYYY');

SELECT * FROM T1 WHERE CREATED >= TRUNC(ADD_MONTHS(SYSDATE, -12),'YYYY') AND CREATED < TRUNC(SYSDATE,'YYYY');--去年

需要注意的是,如果SELECT需要检索的字段只包含索引列且WHERE查询中的索引列含有非空约束的时候,以上规则并不适用。例如,SQL语句“SELECT CREATED FROM T1 WHERE TRUNC(CREATED)=TRUNC(SYSDATE);”,若CREATED列上有非空约束或在WHERE子句中加上“CREATED IS NOT NULL”,则该SQL语句仍然会走索引,如下所示:

代码语言:javascript
复制
DROP TABLE T  PURGE;
CREATE TABLE T  NOLOGGING AS SELECT *  FROM    DBA_OBJECTS D ;
CREATE   INDEX IND_OBJECTNAME ON  T(OBJECT_NAME); 

SELECT T.OBJECT_NAME FROM T WHERE T.OBJECT_NAME ='T';   --走索引
SELECT T.OBJECT_NAME FROM T WHERE UPPER(T.OBJECT_NAME) ='T';     --不走索引
SELECT T.OBJECT_NAME FROM T WHERE UPPER(T.OBJECT_NAME) ='T' AND T.OBJECT_NAME IS NOT NULL ;    --走索引(INDEX FAST FULL SCAN)
SELECT T.OBJECT_NAME FROM T WHERE UPPER(T.OBJECT_NAME) ||'AAA' ='T'||'AAA' AND T.OBJECT_NAME IS NOT NULL ;     --走索引(INDEX FAST FULL SCAN)
SELECT T.OBJECT_NAME,T.OWNER FROM T WHERE UPPER(T.OBJECT_NAME) ||'AAA' ='T'||'AAA' AND T.OBJECT_NAME IS NOT NULL ;     --不走索引

(20)合理使用分析函数。

(21)应尽量避免在WHERE子句中使用不等操作符(!=或<>),否则引擎将放弃使用索引而进行全表扫描。

(22)避免不必要和无意义的排序。

(23)尽可能减少关联表的数量,关联表尽量不要超过3张。

(24)在建立复合索引时,尽量把最常用、重复率低的字段放在最前面。在查询的时候,WHERE条件尽量要包含索引的第一列即前导列。

(25)应尽量避免在WHERE子句中对字段进行IS NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描。可以通过加伪列创建伪联合索引来使得IS NULL使用索引。例如语句:“SELECT ID FROM T WHERE NUM IS NULL;”可以在NUM上设置默认值0,确保表中NUM列没有NULL值,然后这样查询:“SELECT ID FROM T WHERE NUM=0;”。

(26)IN要慎用,因为IN会使系统无法使用索引,而只能直接搜索表中的数据。如:

代码语言:javascript
复制
SELECT ID FROM T WHERE NUM IN (1,2,3);
对于连续的数值,能用BETWEEN就不要用IN了:
SELECT ID FROM T WHERE NUM BETWEEN 1 AND 3;

(27)必要时使用Hint强制查询优化器使用某个索引,如在WHERE子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。

(28)在条件允许的情况下,只访问索引,从而可以避免索引回表读(TABLE ACCESS BY INDEX ROWID,通过索引再去读表中的内容)。当索引中包括处理查询所需要的所有数据时,可以执行只扫描索引操作,而不用做索引回表读操作。因为索引回表读开销很大,能避免则避免。避免的方法就是,①根据业务需求只留下索引字段;②建立联合索引。这里的第二点需要注意平衡,如果联合索引的联合列太多,必然导致索引过大,虽然消减了回表动作,但是索引块变多,在索引中的查询可能就要遍历更多的BLOCK了,所以需要全面考虑,联合索引列不宜过多,一般来说超过3个字段组成的联合索引都是不合适的,需要权衡利弊。

(29)选择合适的索引。Oracle在进行一次查询时,一般对一个表只会使用一个索引。例如,某表有索引1(POLICYNO)和索引2(CLASSCODE),如果查询条件为POLICYNO ='XX' AND CLASSCODE ='XX',那么系统有可能会使用索引2,相较于使用索引1,查询效率明显降低。

(30)优先且尽可能使用分区索引。

(31)在删除(DELETE)、插入(INSERT)、更新(UPDATE)频繁的表中,建议不要使用位图索引。

(32)对于分区表,应该减少需要扫描的分区,避免全分区扫描。对于单分区扫描,在分区表后加上PARTITION(分区名);对于多分区扫描,使用分区关键字来限制需要扫描的范围,从而可以避免全分区扫描。

(33)使用分批处理、DBMS_PARALLEL_EXECUTE进行处理。

(34)删除重复记录尽量采用ROWID的方法,如下所示:

DELETE FROM SCOTT.EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM SCOTT.EMP X WHERE X.EMPNO = E.EMPNO);

(35)SQL中慎用自定义函数。如果自定义函数的内容,只是针对函数输入参数的运算,而没有访问表这样的代码,那么这样的自定义函数在SQL中直接使用是高效的;否则,如果函数中含有对表的访问的语句,那么在SQL中调用该函数很可能会造成很大的性能问题,需要谨慎!在这种情况下,往往将函数中访问表的代码取出和调用它的SQL整合成新的SQL。

(36)使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表,这对于大表非常有效,如下所示:

代码语言:javascript
复制
SELECT COUNT(*), SUM(SAL) FROM SCOTT.EMP WHERE DEPTNO = 20 AND ENAME LIKE 'SMITH%';
SELECT COUNT(*), SUM(SAL) FROM SCOTT.EMP WHERE DEPTNO = 30 AND ENAME LIKE 'SMITH%';

--若使用DECODE函数则对SCOTT.EMP表只访问一次,如下所示:
SELECT COUNT(DECODE(DEPTNO, 20, '1', NULL)) D20_COUNT,  COUNT(DECODE(DEPTNO, 30, '1', NULL)) D30_COUNT,
       SUM(DECODE(DEPTNO, 20, SAL, NULL)) D20_SAL, SUM(DECODE(DEPTNO, 30, SAL, NULL)) D30_SAL
  FROM SCOTT.EMP
 WHERE ENAME LIKE 'SMITH%';

类似的,DECODE函数也可以运用于GROUP BY和ORDER BY子句中。

(37)在计算表的行数时,若表上有主键,则尽量使用COUNT(*)或COUNT(1)。

(38)用WHERE子句替换HAVING子句。避免使用HAVING子句,因为HAVING只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序、总计等操作。如果能通过WHERE子句限制记录的数目,那么就能提高SQL的性能。如下所示:

代码语言:javascript
复制
--低效:
SELECT T.EMPNO, COUNT(*) FROM SCOTT.EMP T GROUP BY T.EMPNO HAVING EMPNO = 7369;

--高效:
SELECT T.EMPNO, COUNT(*) FROM SCOTT.EMP T WHERE EMPNO = 7369 GROUP BY T.EMPNO ;

(39)减少对表的查询,尤其是要避免在同一个SQL中多次访问同一张大表。可以考虑如下的改写方法:

① 先根据条件提取数据到临时表中,然后再做连接,即利用WITH进行改写。

② 有的相似的语句可以用MAX+DECODE函数来处理。

③ 在含有子查询的SQL语句中,要特别注意减少对表的查询,例如形如“UPDATE AAA T SET T.A=(....) T.B=(....) WHERE ....;”该更新的SQL语句中小括号中的大表都是一样的,且查询非常相似,这个时候可以修改为:“UPDATE AAA T SET (T.A,T.B)=(.....) WHERE ....;”。

(40)SQL语句统一使用大写。因为Oracle总是先解析SQL语句,把小写的字母转换成大写的再执行。

(41)对于一些固定性的小的查询结果集或统计性的SQL语句(例如,SQL语句非常复杂,但是最终返回的结果集很简单,只包含少数的几行数据)可以使用结果集缓存(Result Cache)。对于一些常用的小表可以使用保留池(Keep Pool)。

(42)如果在一条SQL语句中同时取最大值和最小值,那么需要注意写法上的差异:

代码语言:javascript
复制
SELECT MAX(OBJECT_ID),MIN(OBJECT_ID) FROM T; --效率差,选择INDEX FAST FULL SCAN
SELECT MAX_VALUE, MIN_VALUE FROM (SELECT MAX(OBJECT_ID) MAX_VALUE FROM T) A, (SELECT MIN(OBJECT_ID) MIN_VALUE FROM T) B;--效率高,选择INDEX FULL SCAN (MIN/MAX)

示例如下所示:

准备环境:

代码语言:javascript
复制
DROP TABLE T_20170704_LHR_01 PURGE;
CREATE TABLE T_20170704_LHR_01 AS SELECT * FROM DBA_OBJECTS;
UPDATE T_20170704_LHR_01 SET OBJECT_ID=ROWNUM;
COMMIT;
ALTER TABLE T_20170704_LHR_01 ADD CONSTRAINT PK_20170704_OBJECT_ID PRIMARY KEY (OBJECT_ID);

普通写法:

代码语言:javascript
复制
LHR@orclasm > SET AUTOTRACE ON
LHR@orclasm > SET LINESIZE 1000
LHR@orclasm > SELECT MAX(OBJECT_ID),MIN(OBJECT_ID) FROM T_20170704_LHR_01;

MAX(OBJECT_ID) MIN(OBJECT_ID)
-------------- --------------
         79298              1


Execution Plan
----------------------------------------------------------
Plan hash value: 2419726051

-----------------------------------------------------------------------------------------------
| Id  | Operation             | Name                  | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                       |     1 |    13 |    51   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE       |                       |     1 |    13 |            |          |
|   2 |   INDEX FAST FULL SCAN| PK_20170704_OBJECT_ID | 76600 |   972K|    51   (2)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        172  consistent gets
          0  physical reads
          0  redo size
        613  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

优化后的写法:

代码语言:javascript
复制
LHR@orclasm > SELECT MAX_VALUE, MIN_VALUE FROM (SELECT MAX(OBJECT_ID) MAX_VALUE FROM T_20170704_LHR_01) A, (SELECT MIN(OBJECT_ID) MIN_VALUE FROM T_20170704_LHR_01) B;

 MAX_VALUE  MIN_VALUE
---------- ----------
     79298          1


Execution Plan
----------------------------------------------------------
Plan hash value: 3965153161

------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                  | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                       |     1 |    26 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |                       |     1 |    26 |     4   (0)| 00:00:01 |
|   2 |   VIEW                       |                       |     1 |    13 |     2   (0)| 00:00:01 |
|   3 |    SORT AGGREGATE            |                       |     1 |    13 |            |          |
|   4 |     INDEX FULL SCAN (MIN/MAX)| PK_20170704_OBJECT_ID |     1 |    13 |     2   (0)| 00:00:01 |
|   5 |   VIEW                       |                       |     1 |    13 |     2   (0)| 00:00:01 |
|   6 |    SORT AGGREGATE            |                       |     1 |    13 |            |          |
|   7 |     INDEX FULL SCAN (MIN/MAX)| PK_20170704_OBJECT_ID |     1 |    13 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        603  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

无论是从cost还是逻辑读方面,差异都是非常大的,因为优化后的SQL选择的是“INDEX FULL SCAN (MIN/MAX)”,性能大幅度提升。

(43)在PL/SQL中,在定义变量类型时尽量使用%TYPE和%ROWTYPE,这样可以减少代码的修改,增加程序的可维护性。

以上讲解的每点优化内容希望读者可以通过实验来加深理解。

本文选自《Oracle程序员面试笔试宝典》,作者:李华荣。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DB宝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档