您的位置:首页 > 数据库 > Oracle

oracle 性能优化建议小结

2014-08-06 12:04 405 查看
平时关注Oracle数据库的网友都知道,Oracle性能优化保证了Oracle数据库的健壮性。下面就此提出需要注意的两个原则。

原则一:注意WHERE子句中的连接顺序:

ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.

尤其是“主键ID=?”这样的条件。

原则二: SELECT子句中避免使用 ‘ * ‘:

ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间 。

简单地讲,语句执行的时间越短越好(尤其对于系统的终端用户来说)。而对于查询语句,由于全表扫描读取的数据多,尤其是对于大型表不仅查询速度慢,而且对磁盘IO造成大的压力,通常都要避免,而避免的方式通常是使用索引Index。

使用索引的优势与代价。

优势:


1)索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构. 通常,通过索引查询数据比全表扫描要快. 当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引. 同样在联结多个表时使用索引也可以提高效率.

2) 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证.。那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列. 通常, 在大型表中使用索引特别有效. 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率.

代价: 虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会 使查询反应时间变慢.。而且表越大,影响越严重。

使用索引需要注意的地方:

1、避免在索引列上使用NOT , 

我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响. 当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描.

2、避免在索引列上使用计算.

WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描. 举例:

复制代码代码如下:

低效:SELECT … FROM DEPT WHERE SAL * 12 > 25000;

高效:SELECT … FROM DEPT WHERE SAL > 25000/12;

3、避免在索引列上使用IS NULL和IS NOT NULL

避免在索引中使用任何可以为空的列,ORACLE性能上将无法使用该索引.对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中.举例: 如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入). 然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空. 因此你可以插入1000
条具有相同键值的记录,当然它们都是空! 因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.

复制代码代码如下:

低效:(索引失效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL;

高效:(索引有效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;

4、注意通配符%的影响

使用通配符的情况下Oracle可能会停用该索引。如 :

复制代码代码如下:

SELECT…FROM DEPARTMENT WHERE DEPT_CODE like ‘%123456%'(无效)。

SELECT…FROM DEPARTMENT WHERE DEPT_CODE = ‘123456'(有效)

5、避免改变索引列的类型.:

当比较不同数据类型的数据时, 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 WHERETO_NUMBER(EMP_TYPE)=123 因为内部发生的类型转换, 这个索引将不会被用到! 为了避免ORACLE对你的SQL进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE会优先转换数值类型到字符类型

6、索引的一些“脾气”

a. 如果检索数据量超过30%的表中记录数.使用索引将没有显著的效率提高.

b. 在特定情况下, 使用索引也许会比全表扫描慢, 但这是同一个数量级上的区别. 而通常情况下,使用索引比全表扫描要块几倍乃至几千倍!

除了使用索引,我们还有其他能减少资源消耗的方法:

1、用EXISTS替换DISTINCT:


当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换, EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果.

例子:

复制代码代码如下:

(低效): SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E

WHERE D.DEPT_NO = E.DEPT_NO

And E.sex =man

(高效): SELECT DEPT_NO,DEPT_NAME FROM DEPT D

WHERE EXISTS

( SELECT ‘X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO

And E.sex =man

);

2、用(UNION)UNION ALL替换OR (适用于索引列)

通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描.

注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低. 在下面的例子中, LOC_ID 和REGION上都建有索引.

如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面.

复制代码代码如下:

高效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 UNION ALL

SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE REGION = “MELBOURNE”

低效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = “MELBOURNE”

3、用UNION-ALL 替换UNION ( 如果有可能的话):

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序. 如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高. 需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是要从业务需求分析使用UNION ALL的可行性. UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这块内存的优化也是相当重要的.

4、Order By语句加在索引列,最好是主键PK上。

复制代码代码如下:

SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE(低效)

SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_CODE (高效)

5、避免使用耗费资源的操作:

带有DISTINCT,UNION,MINUS,INTERSECT的SQL语句会启动SQL引擎 执行耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序. 通常, 带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写. 如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强

6、使用Where替代Having(如果可以的话)

优化GROUP BY:

提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.

复制代码代码如下:

低效:

SELECT JOB , AVG(SAL)

FROM EMP GROUP JOB HAVING JOB = ‘PRESIDENT'AND AVG(SAL)>XXX

高效:

SELECT JOB , AVG(SAL)

FROM EMP

WHERE JOB = ‘PRESIDENT'

OR JOB = ‘MANAGER' GROUP JOB Having AND AVG(SAL)>XXX

7、通常来说,如果语句能够避免子查询的 使用,就尽量不用子查询。因为子查询的开销是相当昂贵的

很简单的一次调整,语句加了适当的索引后性能就有大幅的提升。当时看到这条语句的时候,第一感觉就是执行效率肯定低下。语句的功能是求某一客户当天产品的总销量。

原来的语句是这样的:

  select sum(sl0000) from xstfxps2 where

  dhao00 in (

  select dhao00 from xstfxps1 where trunc(ywrq00)=trunc(sysdate)

  and khdm00='500000003913');

  已用时间: 00: 02: 49.04

  

  Execution Plan

  ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=CHOOSE

  1 0 SORT (AGGREGATE)

  2 1 NESTED LOOPS

  3 2 TABLE ACCESS (FULL) OF 'XSTFXPS2'

  4 2 TABLE ACCESS (BY INDEX ROWID) OF 'XSTFXPS1'

  5 4 INDEX (UNIQUE SCAN) OF 'XSTFXPS1_PK' (UNIQUE)

  

  Statistics

  ----------------------------------------------------------

  0 recursive calls

  0 db block gets

  17355138 consistent gets

  34141 physical reads

  2912 redo size

  198 bytes sent via SQL*Net to client

  275 bytes received via SQL*Net from client

  2 SQL*Net roundtrips to/from client

  0 sorts (memory)

  0 sorts (disk)

  1 rows processed

  我们看到统计信息里面进行了17355138次逻辑读,34141次物理IO,这是相当吓人的数字。在执行计划里面我们看到表XSTFXPS2来了一次全表扫描。

  我们首先看一下这两张表总的数据量:

  SQL> select count(*) from xstfxps2;

  

  COUNT(*)

  ----------

  5585018

  我们这里看到XSTFXPS2这张表有5585018条记录。

  SQL> select count(*) from xstfxps1;

  

  COUNT(*)

  ----------

  702121

  两张表的表结构如下所示:

  SQL> desc xstfxps1

  Name Type Nullable Default Comments

  ------ ------------ -------- ------- --------

  DHAO00 NUMBER(8)

  LHDH00 NUMBER(8) Y

  FLDH00 NUMBER(8) Y

  FPLB00 VARCHAR2(2) Y

  YWRQ00 DATE Y

  YWRY00 VARCHAR2(8) Y

  SHRQ00 DATE Y

  XSQRRQ DATE Y

  XSQRRY VARCHAR2(8) Y

  KHDM00 VARCHAR2(12)

  XKZH00 VARCHAR2(12)

  CKDM00 VARCHAR2(2) Y

  THCKDM VARCHAR2(2) Y

  XSFSDM VARCHAR2(2) Y

  FXRYDM VARCHAR2(4) Y

  SHRYDM VARCHAR2(4) Y

  SHBJ00 VARCHAR2(1) 'N'

  FXBJ00 VARCHAR2(1) 'N'

  SKBJ00 VARCHAR2(2) Y

  FKDM00 VARCHAR2(2) Y

  

  SQL> desc xstfxps2

  Name Type Nullable Default Comments

  ------ ------------ -------- ------- --------

  DHAO00 NUMBER(8)

  SPDM00 VARCHAR2(8)

  DJIA00 NUMBER(7,2) 0

  FXSL00 NUMBER Y 0

  SL0000 NUMBER Y 0

  THSL00 NUMBER Y 0

  JE0000 NUMBER Y 0

  SE0000 NUMBER Y

  FPBBH0 VARCHAR2(11) Y

  FPHAO0 VARCHAR2(10) Y

  RBDH00 NUMBER(8) Y

  

  其中XSTFXPS1的客户订单的表头,保存订单的客户信息、订货日期等信息。XSTFXPS2是订单的表体,详细记录了客户订单的商品、价格、数量等信息。

  

  调整的第一步是把子查询提取出来,再看语句的执行计划。通常来说,如果语句能够避免子查询的使用,就尽量不用子查询。因为子查询的开销是相当昂贵的。改写后的语句如下:

  select sum(sl0000)

  from xstfxps2 a,(select dhao00 from xstfxps1 where trunc(ywrq00)=trunc(sysdate)

  and khdm00='500000003913') b

  where a.dhao00=b.dhao00;

  已用时间: 00: 00: 03.05

  Execution Plan

  ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=CHOOSE

  1 0 SORT (AGGREGATE)

  2 1 TABLE ACCESS (BY INDEX ROWID) OF 'XSTFXPS2'

  3 2 NESTED LOOPS

  4 3 TABLE ACCESS (FULL) OF 'XSTFXPS1'

  5 3 INDEX (RANGE SCAN) OF 'XSTFXPS2_PK' (UNIQUE)

  Statistics

  ----------------------------------------------------------

  0 recursive calls

  0 db block gets

  11974 consistent gets

  225 physical reads

  832 redo size

  211 bytes sent via SQL*Net to client

  275 bytes received via SQL*Net from client

  2 SQL*Net roundtrips to/from client

  0 sorts (memory)

  0 sorts (disk)

  1 rows processed

  

  我们可以看到逻辑IO由原来的17355138次下降到11974次,有了数量级的提升。执行时间也有原来将近3分钟下降到现在的3秒多一些。很显然性能有了大幅的提升。不过我们看到执行计划里面表XSTFXPS1还是有一个全表扫描存在。通常来说我们应该尽量避免全表扫描的存在,尤其对于大表,应该建立合适的索引以避免FTS的产生。我们来看这两张表的索引信息:

  

  select index_name,column_name from dba_ind_columns where table_name like 'XSTFXPS%'

  INDEX_NAME COLUMN_NAME

  ------------------------------ -----------------------------------

  XSTFXPS1_PK DHAO00

  XSTFXPS2_PK DHAO00

  XSTFXPS2_PK SPDM00

  

  我们看到这两张表除了主键约束外都没有建另外的索引。根据语句的查询情况,我们建立了如下的复合索引:

  create index idx_xstfxps1_khdm00_ywrq00 on xstfxps1(khdm00,ywrq00) tablespace indx;

  

  为了使用索引,我们必须对原来的日期字段的条件进行一些调整。因为有个trunc()函数的存在,语句将不会使用到索引。我们只要明白trunc(ywrq00)=trunc(sysdate)事实上等同于ywrq00大于trunc(sysdate),小于trunc(sysdate+1)减去一秒,我们就有了比较好的办法来处理

  这个条件。最终改写后的语句如下:

  select sum(sl0000)

  from xstfxps2 a, xstfxps1 b

  where a.dhao00=b.dhao00

  and b.khdm00='500000003913'

  and b.ywrq00 between trunc(sysdate)

  and trunc(sysdate)+1-1/(24*60*60);

  Execution Plan

  ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=CHOOSE

  1 0 SORT (AGGREGATE)

  2 1 TABLE ACCESS (BY INDEX ROWID) OF 'XSTFXPS2'

  3 2 NESTED LOOPS

  4 3 TABLE ACCESS (BY INDEX ROWID) OF 'XSTFXPS1'

  5 4 INDEX (RANGE SCAN) OF 'IDX_XSTFXPS1_KHDM00_YWRQ00'

  (NON-UNIQUE)

  

  6 3 INDEX (RANGE SCAN) OF 'XSTFXPS2_PK' (UNIQUE)

  Statistics

  ----------------------------------------------------------

  0 recursive calls

  0 db block gets

  3 consistent gets

  0 physical reads

  0 redo size

  210 bytes sent via SQL*Net to client

  275 bytes received via SQL*Net from client

  2 SQL*Net roundtrips to/from client

  0 sorts (memory)

  0 sorts (disk)

  1 rows processed

  我们这时候看逻辑IO已经降为3次,语句的执行计划也符合我们的调整目标,创建的索引产生了比较大的效果。这条语句的调整至此告一段落。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: