Oracle 高效SQL

 

Oracle 高效SQL

 

 

Posted on 2006-12-30 15:51:17 in 网站制作技术

<http://bbbh.org/Website-Develop/> 

 

==========搜了蛮多的相关内容,但这篇好像包括了所有

===http://bbbh.org/20070910/16129/============

 

 

No1:选择合适的优化器 

 

 

No2:共享SQL 

 

为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中.这

块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内

存可以被所有的数据库用户共享. 因此,当你执行一个SQL语句(有时被称为一个游标)

时,如果它 

和之前的执行过的语句完全相同, ORACLE就能很快获得已经被解析的语句以及最好的 

执行路径. ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用. 

     可惜的是ORACLE只对简单的表提供高速缓冲(cache buffering) ,这个功能并不适

用于多表连接查询. 

数据库管理员必须在init.ora中为这个区域设置合适的参数,当这个内存区域越大,就可

以保留更多的语句,当然被共享的可能性也就越大了. 

当你向ORACLE 提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句. 

 这里需要注明的是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须 

完全相同(包括空格,换行等). 

共享的语句必须满足三个条件: 

 A.      字符级的比较: 

B.      两个语句所指的对象必须完全相同: 

 C.      两个SQL语句中必须使用相同的名字的绑定变量(bind variables) 

 

No3. 选择最有效率的表名顺序(只在基于规则的优化器中有效) 

 

ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后

的表(基础表 driving table)将被最先处理. 在FROM子句中包含多个表的情况下,你必

须选择记录条数最少的表作为基础表.当ORACLE处理多个表时, 会运用排序及合并的方

式连接它们.首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序,然后扫

描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一

个表中合适记录进行合并. 

如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表,

交叉表是指那个被其他表所引用的表. 

 

No4:WHERE子句中的连接顺序 

 

ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他

WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾. 

 

No5:避免使用select * 

 

当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ‘*’ 是一个方便的

方法.不幸的是,这是一个非常低效的方法. 实际上,ORACLE在解析的过程中, 会将’*’

依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的

时间. 

 

No6:减少访问数据库的次数 

 

当执行每条SQL语句时, ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用

率, 绑定变量 , 读数据块等等. 由此可见, 减少访问数据库的次数 , 就能实际上减少

ORACLE的工作量. 

 

No7:用TRUNCATE替代DELETE 

 

当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢

复的信息. 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是

恢复到执行删除命令之前的状况) 

而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被

恢复.因此很少的资源被调用,执行时间也会很短. 

 

No8:适当的使用commit 

 

这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少: 

 COMMIT所释放的资源: 

a.       回滚段上用于恢复数据的信息. 

b.       被程序语句获得的锁 

c.       redo log buffer 中的空间 

d.       ORACLE为管理上述3种资源中的内部花费 

在使用COMMIT时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌

不可得兼,另外过于频繁的使用commit效率也会降低。 

 

No9:减少对表的查询 

 

在含有子查询的SQL语句中,要特别注意减少对表的查询. 

Update 多个Column 例子: 

     低效: 

           UPDATE EMP 

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

              SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES) 

           WHERE EMP_DEPT = 0020; 

     高效: 

           UPDATE EMP 

           SET (EMP_CAT, SAL_RANGE) 

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

 FROM EMP_CATEGORIES) 

           WHERE EMP_DEPT = 0020; 

 

No10:IN与EXIST 

 

先说IN 

他相当对inner table执行一个个带有distinct的子查询语句,然后得到的查询结果集

再与outer table进行连接,当然连接的方式和索引的使用仍然同于普通的两表连接。 

select * from T1 where x in (select y from T2); 

可以转换成如下 

select * from 

T1,(select distinct y from T2) T2 

where T1.x=T2.y; 

再说exists 

实际上exists相当于对outer table进行全表扫描,用从中检索到的每一行与inner

table做循环匹配输出相应的符合条件的结果,其主要开销是对outer table的全表扫描

(full scan),而连接方式是nested loop方式。 

可以写成 

select * from T1 where exists (select NULL from T2 where T2.y=T1.x); 

转换成 

for cursor1 in (select * from T1) 

loop 

if (exists (select NULL from T2 where T2.y=cursor1.x)) 

then 

             返回匹配的记录; 

end if; 

end loop; 

通过上面的解释,现在很容易明白当T2数据量巨大且索引情况不好(大量重复值等),则

不宜使用产生对T2的distinct检索而导致系统开支巨大的IN操作,反之当T1表数据量巨

大(不受索引影响)而T2表数据较少且索引良好则不宜使用引起T1全表扫描的EXISTS操作

 

 

No11:NOT IN, NOT EXIST 

 

在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是

最低效的 (因为它对子查询中的表执行了一个全表遍历).  为了避免使用NOT IN ,我们

可以把它改写成外连接(Outer Joins)或NOT EXISTS. 

 

No12:使用表连接连替换EXIST 

 

通常来说 , 采用表连接的方式比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’ ; 

 

No13:用索引提高效率 

 

 

1.索引基础 

 

索引是表的一个概念部分,用来提高检索数据的效率. 实际上,ORACLE使用了一个复杂的

自平衡B-tree结构. 通常,通过索引查询数据比全表扫描要快. 当ORACLE找出执行查询

和Update语句的最佳路径时, ORACLE优化器将使用索引. 同样在联结多个表时使用索引

也可以提高效率. 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验

证. 

除了那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列. 通常, 在大型表中使用

索引特别有效. 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率. 

虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间

来 

存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修

改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O

. 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢.

 

ORACLE对索引有两种访问模式. 

索引唯一扫描 ( INDEX UNIQUE SCAN) 

大多数情况下, 优化器通过WHERE子句访问INDEX. 

例如: 

表LODGING有两个索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER

列上的非唯一性索引LODGING$MANAGER. 

SELECT * 

FROM LODGING 

WHERE LODGING = ‘ROSE HILL’; 

   在内部 , 上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫

描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式 执行下一步检索. 

   如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访

问表). 因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果. 

   下面SQL只需要INDEX UNIQUE SCAN 操作. 

 

        SELECT LODGING 

        FROM  LODGING 

WHERE LODGING = ‘ROSE HILL’; 

  索引范围查询(INDEX RANGE SCAN) 

      适用于两种情况: 

1.       基于一个范围的检索 

2.       基于非唯一性索引的检索 

 例1: 

      SELECT LODGING 

      FROM  LODGING 

WHERE LODGING LIKE ‘M%’; 

WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK . 由

于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描 

低一些. 

例2: 

      SELECT LODGING 

      FROM  LODGING 

WHERE MANAGER = ‘BILL GATES’; 

  这个SQL的执行分两步, LODGING$MANAGER的索引范围查询(得到所有符合条件记录的

ROWID) 和下一步同过ROWID访问表得到LODGING列的值. 由于LODGING$MANAGER是一个非

唯一性的索引,数据库不能对它执行索引唯一扫描. 

  由于SQL返回LODGING列,而它并不存在于LODGING$MANAGER索引中, 所以在索引范围查

询后会执行一个通过ROWID访问表的操作. 

 

2.存在下面情况的SQL,不会用到索引 

 

 

  存在数据类型隐形转换的,如:

         select * from staff_member where staff_id=’123’;

  列上有数学运算的,如:

         select * from staff_member where salary*2<10000;

  使用不等于(<> )运算的,如:

         select * from staff_member where dept_no<>2001; 

记住, 索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中.  

使用substr字符串函数的,如:

  select * from staff_member where substr(last_name,1,4)=’FRED’;

 ‘%’通配符在第一个字符的,如:

  select * from staff_member where first_name like ‘%DON’;

字符串连接(||)的,如:

  select * from staff_member where first_name||’’=’DONALD’ 

       避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引 .对于单列索

引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索

引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中. 

       通常, 我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数

相同的 

影响. 当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描. 

如果一定要对使用函数的列启用索引, ORACLE新的功能: 基于函数的索引

(Function-Based Index) 也许是一个较好的方案. 

 CREATE INDEX EMP_I ON EMP (UPPER(ename)); /*建立基于函数的索引*/ 

 SELECT * FROM emp WHERE UPPER(ename) = ‘BLACKSNAIL’; /*将使用索引*/ 

 

3.多个索引情况下的选择 

 

当SQL语句的执行路径可以使用分布在多个表上的多个索引时, ORACLE会同时使用多个

索引并在运行时对它们的记录进行合并, 检索出仅对全部索引有效的记录. 

在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引. 然而这个规则只有 

当WHERE子句中索引列和常量比较才有效.如果索引列和其他表的索引类相比较. 这种子

句在优化器中的等级是非常低的. 

如果不同表中两个相同等级的索引将被引用, FROM子句中表的顺序将决定哪个会被率先

使用. FROM子句中最后的表的索引将有最高的优先级. 

如果相同表中两个想同等级的索引将被引用, WHERE子句中最先被引用的索引将有最高

的优先级. 

举例: 

     DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引. 

     SELECT ENAME, 

     FROM EMP 

     WHERE DEPT_NO = 20 

     AND EMP_CAT = ‘A’; 

这里,DEPTNO索引将被最先检索,然后同EMP_CAT索引检索出的记录进行合并. 执行路径

如下: 

TABLE ACCESS BY ROWID ON EMP 

    AND-EQUAL 

        INDEX RANGE SCAN ON DEPT_IDX 

        INDEX RANGE SCAN ON CAT_IDX 

当ORACLE无法判断索引的等级高低差别,优化器将只使用一个索引,它就是在WHERE子句

中被列在最前面的. 

 

4.自动选择索引 

 

如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性.

 

在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引. 

举例: 

SELECT ENAME 

FROM EMP 

WHERE EMPNO = 2326  

AND DEPTNO  = 20 ; 

这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录. 

TABLE ACCESS BY ROWID ON EMP 

       INDEX UNIQUE SCAN ON EMP_NO_IDX 

 

5.等式比较和范围比较 

 

     当WHERE子句中有索引列, ORACLE不能合并它们,ORACLE将用范围比较. 

     举例: 

     DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引. 

     SELECT ENAME 

     FROM EMP 

     WHERE DEPTNO > 20 

     AND EMP_CAT = ‘A’; 

 

     这里只有EMP_CAT索引被用到,然后所有的记录将逐条与DEPTNO条件进行比较. 执

行路径如下: 

     TABLE ACCESS BY ROWID ON EMP 

           INDEX RANGE SCAN ON CAT_IDX 

3.组合索引总是使用索引的第一个列 

如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用

时,优化器才会选择使用该索引. 

 

6.CBO下使用更具选择性的索引 

 

基于成本的优化器(CBO, Cost-Based Optimizer)对索引的选择性进行判断来决定索引

的使用是否能提高效率. 

如果索引有很高的选择性, 那就是说对于每个不重复的索引键值,只对应数量很少的记

录. 

比如, 表中共有100条记录而其中有80个不重复的索引键值. 这个索引的选择性就是

80/100 = 0.8 . 选择性越高, 通过索引键值检索出的记录就越少. 

如果索引的选择性很低, 检索数据就需要大量的索引范围查询操作和ROWID 访问表的 

操作. 也许会比全表扫描的效率更低. 

下列经验请参阅: 

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

b.       在特定情况下, 使用索引也许会比全表扫描慢, 但这是同一个数量级上的 

区别. 而通常情况下,使用索引比全表扫描要块几倍乃至几千倍! 

 

7.用UNION (ALL)替换OR (适用于索引列) 

 

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

成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询

效率可能会因为你没有选择OR而降低. 

   在下面的例子中, LOC_ID 和REGION上都建有索引. 

高效: 

   SELECT LOC_ID , LOC_DESC , REGION 

   FROM LOCATION 

   WHERE LOC_ID = 10 

   UNION 

   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” 

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

注意: 

WHERE KEY1 = 10   (返回最少记录) 

OR KEY2 = 20        (返回最多记录) 

ORACLE 内部将以上转换为 

WHERE KEY1 = 10 AND 

((NOT KEY1 = 10) AND KEY2 = 20) 

 

8.用>=替代> 

 

DEPT>3和DEPT>=4两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者

将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录.   

 

No14:用UNION ALL 替换UNION ( 如果有可能的话) 

 

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合

并, 然后在输出最终结果前进行排序. 

如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高. 

 

No15:使用提示(Hints) 

 

对于表的访问,可以使用两种Hints. 

FULL 和 ROWID 

FULL hint 告诉ORACLE使用全表扫描的方式访问指定表. 

例如: 

   SELECT /*+ FULL(EMP) */ * 

   FROM EMP 

   WHERE EMPNO = 7893; 

   ROWID hint 告诉ORACLE使用TABLE ACCESS BY ROWID的操作访问表. 

   通常, 你需要采用TABLE ACCESS BY ROWID的方式特别是当访问大表的时候, 使用这

种方式, 你需要知道ROIWD的值或者使用索引. 

   如果一个大表没有被设定为缓存(CACHED)表而你希望它的数据在查询结束是仍然停

留 

在SGA中,你就可以使用CACHE hint 来告诉优化器把数据保留在SGA中. 通常CACHE hint

和 FULL hint 一起使用. 

例如: 

SELECT /*+ FULL(WORKER) CACHE(WORKER)*/ * 

FROM WORK; 

   索引hint 告诉ORACLE使用基于索引的扫描方式. 你不必说明具体的索引名称 

例如: 

   SELECT /*+ INDEX(LODGING) */ LODGING 

   FROM LODGING 

   WHERE MANAGER = ‘BILL GATES’; 

   在不使用hint的情况下, 以上的查询应该也会使用索引,然而,如果该索引的重复值

过多而你的优化器是CBO, 优化器就可能忽略索引. 在这种情况下, 你可以用INDEX

hint强制ORACLE使用该索引. 

   ORACLE hints 还包括ALL_ROWS, FIRST_ROWS, RULE,USE_NL, USE_MERGE, USE_HASH

等等. 

   使用hint , 表示我们对ORACLE优化器缺省的执行路径不满意,需要手工修改.这是一

个很有技巧性的工作. 我建议只针对特定的,少数的SQL进行hint的优化.对ORACLE的优

化器还是要有信心(特别是CBO) 

 

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

 

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎 

执行耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要执行

两次排序. 

例如,一个UNION查询,其中每个查询都带有GROUP BY子句, GROUP BY会触发嵌入排序

(NESTED SORT) ; 这样, 每个查询需要执行一次排序, 然后在执行UNION时, 又一个唯

一排序(SORT UNIQUE)操作被执行而且它只能在前面的嵌入排序结束后才能开始执行.

嵌入的排序的深度会大大影响查询的效率. 

通常, 带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写. 

    如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是

可以考虑的, 毕竟它们的可读性很强 

 

No17:分离表和索引 

 

总是将你的表和索引建立在不同的表空间内(TABLESPACES). 决不要将不属于ORACLE内

部系统的对象存放到SYSTEM表空间里. 同时,确保数据表空间和索引表空间置与不同的

硬盘控制卡控制的硬盘上. 

 

No18:排序发生的情况 

 

 

  SQL中包含group by 子句

  SQL 中包含order by 子句

  SQL 中包含 distinct 子句

  SQL 中包含 minus 或 union操作

  创建索引时 

这些情况慢。 

 

No19:execute immediate, DBMS_SQL 

 

动态SQL中,尽量多用execute immediate,而少用DBMS_SQL,前者综合效率优于后者 

 

No20:用like替换和substr 

 

 

对于‘like’和‘substr’,其效率并没有多大分别。但是,当所搜索的值不存在时,

使用‘like’的速度明显大于‘substr’。

所以:select  *  from  a  where  substr(a1,1,4) = '5378'  可以用like替代

select  *  from  a  where  a1  like  ‘5378%’ 

 

No20:DML语句优化 

 

       1.如果有可能的话truncate 替代delete 

       2.大表的删除转化为对剩余部分建表,truncate原表然后将新建的表改名为原

表. 

       3.Update多列的时候,尽量不要用多个set;如: 

UPDATE EMP 

           SET (EMP_CAT, SAL_RANGE) 

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

             FROM EMP_CATEGORIES) 

           WHERE EMP_DEPT = 0020; 

       4.如果有索引,删除索引后执行操作,操作完成后重建索引。 

       5.环境允许的话使用并行 

 

No21:使用并行 

 

hint /*+ parallel(tablename,parallel-degree)*/ 

调整并行执行的目的是:最大地发挥硬件的能力。如果你有一个高性能的系统,有高优

先的SQL语句在运行,则并行语句就可以使用所有有效的资源。Oracle可以执行的下面

的并行: 

l       并行查询; 

l       并行DML(包括 INSERT, UPDATE, DELETE; APPEND提示,并行索引扫描); 

l       并行 DDL; 

如果你的系统缺少以下这些特点,则并行可能不会有多大改善。 

l       对称多处理器(SMP), 集群或强大的并行系统; 

l       有效的I/O带宽; 

l       低利用的或闲置的CPU(如CPU使用小于30%); 

l       对附加的内存无效,如分类、哈西索引及I/O缓冲区等。 

如果指定的并行度大于实际可用的资源(硬件资源>parallel_max_server>你指定的并行

度),将会使用最大的可用资源的并行度来处理。 

如果多人同时使用并行, sum(parallel_degree)>parallel_max_server ;可能产生等

待使效率下降。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值