Oracle 高效SQL

发表于:2007-07-02来源:作者:点击数: 标签:
No1:选择合适的优化器No2:共享SQL为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中.这块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内存可以被所有的 数据库 用户共享. 因此,当你执行一个SQL语句
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) T2where 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)loopif (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 LODGINGWHERE LODGING = ‘ROSE HILL’;    在内部 , 上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式 执行下一步检索.   如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访问表). 因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果.    下面SQL只需要INDEX UNIQUE SCAN 操作.                SELECT LODGING        FROM  LODGINGWHERE LODGING = ‘ROSE HILL’;   索引范围查询(INDEX RANGE SCAN)      适用于两种情况:1.       基于一个范围的检索2.       基于非唯一性索引的检索  例1:       SELECT LODGING      FROM  LODGINGWHERE LODGING LIKE ‘M%’; WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK . 由于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描低一些.  例2:       SELECT LODGING      FROM  LODGINGWHERE 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 ENAMEFROM EMPWHERE EMPNO = 2326  AND DEPTNO  = 20 ; 这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录.TABLE ACCESS BY ROWID ON EMP       INDEX UNIQUE SCAN ON EMP_NO_IDX5.等式比较和范围比较     当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 和 ROWIDFULL 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 ;可能产生等待使效率下降。 

原文转自:http://www.ltesting.net