Oracle优化规则

女生的网名这么多〃 提交于 2020-11-02 13:45:25
  1. SQL语句执行步骤

语法分析> 语义分析> 视图转换 >表达式转换> 选择优化器 >选择连接方式 >选择连接顺序 >选择数据的搜索路径 >运行“执行计划”

  1. 选用适合的Oracle优化器

RULE(基于规则) COST(基于成本) CHOOSE(选择性)

  1. 访问Table的方式

全表扫描

全表扫描就是顺序地访问表中每条记录,ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描。

通过ROWID访问表

ROWID包含了表中记录的物理位置信息,ORACLE采用索引实现了数据和存放数据的物理位置(ROWID)之间的联系,通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高。

  1. 共享 SQL 语句

Oracle提供对执行过的SQL语句进行高速缓冲的机制。被解析过并且确定了执行路径的SQL语句存放在SGA的共享池中。
Oracle执行一个SQL语句之前每次先从SGA共享池中查找是否有缓冲的SQL语句,如果有则直接执行该SQL语句。
可以通过适当调整SGA共享池大小来达到提高Oracle执行性能的目的。

OLTP 联机事务处理,适合使用共享SQL语句来达到优化的目的,eg : Java 中的PrepareStatement, Oracle中的变量绑定.SELECT 1 FROM EMP WHERE NAME=:x USING :VNAME;

  1. 选择最有效率的连接方式
    Merge Sort Join
    按照Merge Sort Join连接的两表地位完全相同。这种算法会把每个表按照连接列进行排序,生成两个排序集。然后对两个排序集进行一次遍历便可以得到最终结果集。这个算法的特点是,每个表都需要排序,排序后都需要遍历一次。
    以下面的例子说明,Merge Sort Join的执行过程如下:
    SQL> explain plan for select /+ use_merge(tabs,cols) /
      2   from tabs,cols where tabs.table_name=cols.table_name and tabs.owner like 'SC%' ;
    Explained.
    SQL>  select


    from table(dbms_xplan.display);
    PLAN_TABLE_OUTPUT




    Plan hash value: 2945511789

    | Id  | Operation           | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |

    |   0 | SELECT STATEMENT    |      |   227 | 66738 |       |  1264   (1)| 00:00:16 |
    |   1 |  MERGE JOIN         |      |   227 | 66738 |       |  1264   (1)| 00:00:16 |
    |   2 |   SORT JOIN         |      |     5 |  1025 |       |    13   (8)| 00:00:01 |
    |  3 |    TABLE ACCESS FULL| TABS |     5 |  1025 |       |    12   (0)| 00:00:01 |
    |
      4 |   SORT JOIN         |      | 57687 |  5013K|    11M|  1251   (1)| 00:00:16 |
    |   5 |    TABLE ACCESS FULL| COLS | 57687 |  5013K|       |    65   (4)| 00:00:01 |
    PLAN_TABLE_OUTPUT





    Predicate Information (identified by operation id):

       3 - filter("TABS"."OWNER" LIKE 'SC%')
       4 - access("TABS"."TABLE_NAME"="COLS"."TABLE_NAME")
           filter("TABS"."TABLE_NAME"="COLS"."TABLE_NAME")
    Note


    PLAN_TABLE_OUTPUT

       - dynamic sampling used for this statement
    23 rows selected.
    1、根据tabs表的where条件,查找出符合条件的结果集。
    2、以全表扫描的方式,扫描出cols表中的结果集。
    3、按照table_name列对tabs表进行排序
    4、按照table_name列对cols表进行排序
    5、对两个有序的结果集进行合并,并返回最终记录集。
    Sort Merge主要的开销在于对两表连接的排序,已经在连接关联列上已经进行了排序,则该连接操作就不需要再进行 sort 操作,这样可以大大提高这种连接操作的连接速度,特别是对于较大的表。






Nest Loop Join原理
Nest Loop Join是通过两层嵌套循环进行依次的匹配操作,最后返回结果集合。SQL语句只是描述出希望连接的对象和规则,而执行计划和执行操作要切实将一行行的记录进行匹配。
Nest Loop Join的操作过程很简单,很像我们最简单的排序检索算法,两层循环结构。进行连接的两个数据集合(数据表)分别称为驱动表(外侧表)和被驱动表(内测 表)。首先处理驱动表中每一行符合条件的数据,之后每一行数据和被驱动表进行连接匹配操作。最后可以获取到结果集合。NESTED LOOPS等于cost = outer access cost + (inner access cost outer cardinality),故驱动表较小cost较低也就很好理解
NESTED LOOPS 有其它连接方法没有的的一个优点是:
可以先返回已经连接的行,而不必等待所有的连接操作处理完才返回数据,这可以实现快速的响应时间。  这是从连接操作中可以得到第一个匹配行的最快的方法之一,这种类型的连接可以用在需要快速响应的语句中,以响应速度为主要目标。
以下面的例子说明,Nest Loop Join的执行过程如下:
SQL> create table tabs as select



from dba_tables;
Table created
SQL> create table cols as select owner,table_name, column_name, data_type from dba_tab_cols;
Table created
SQL> explain plan for select /+ use_nl(tabs,cols) / from tabs,cols where tabs.table_name=cols.table_name and tabs.owner='SCOTT';
Explained.
SQL> select

from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT






Plan hash value: 1483644320

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

|   0 | SELECT STATEMENT   |      |   228 |   128K|   322   (3)| 00:00:04 |
|   1 |  NESTED LOOPS      |      |   228 |   128K|   322   (3)| 00:00:04 |
|  2 |   TABLE ACCESS FULL| TABS |     5 |  2435 |    12   (0)| 00:00:01 |
|
  3 |   TABLE ACCESS FULL| COLS |    46 |  4094 |    62   (4)| 00:00:01 |

PLAN_TABLE_OUTPUT

Predicate Information (identified by operation id):

   2 - filter("TABS"."OWNER"='SCOTT')
   3 - filter("TABS"."TABLE_NAME"="COLS"."TABLE_NAME")
Note

   - dynamic sampling used for this statement
20 rows selected.
1、读取驱动表tabs表中的第一条符合条件的记录
2、遍历被驱动表COLS,根据条件tabs.table_name=cols.table_name找到符合要求的记录,每找到一条记录立即返回给用户。
3、重复步骤1、2直到驱动表tabs中的记录都被处理,才能完成整个连接操作。
Note:从上述过程来看驱动表与被驱动表的遍历没有先后关系,是同时进行的
该算法的特点:
对驱动表tabs做一次遍历,而对被驱动表cols进行多次遍历,遍历次数等于驱动表tabs中处理的记录数量。
该算法返回第一条记录的速度非常快,不需要排序操作,同时可以用于非等值连接。
Nest Loop Join优化
如上文所述,Nest Loop Join的过程是双层嵌套循环的过程,所以外层嵌套的次数应越少越好,故应将小表或者返回结果集较小的表作为驱动表为佳。大部分情况下遵循这个指导原则会降低SQL的IO。有时不遵循这个原则会得到更好的效率。 
如 果在驱动表或者被驱动表的连接条件列上存在索引,在进行Nest Loop Join的时候,驱动表和被驱动表可以直接确定符合连接条件的被驱动表数据行对应的rowid。不需要直接对被驱动表进行检索,就可以获取到rowid 了。由于索引对应的体积一般要远远小于Inner Table,所以进行的块读取要少很多。所以连接列上存在索引与否,一定程度上也影响了Nest Loop Join的效率。
如 果驱动表比较小,并且在被驱动表上有唯一索引,或有高选择性非唯一索引时,使用这种方法可以得到较好的效率。如果不使用并行操作,最好的驱动表是那些应用 了 where 限制条件后,可以返回较少行数据的的表,所以大表也可能称为驱动表,关键看限制条件。对于并行查询,我们可以选择大表作为驱动表,因为大表可以充分利用并 行功能。当然,有时对查询使用并行操作并不一定会比查询不使用并行操作效率高,因为 
最后可能每个表只有很少的行符合限制条件,而且还要看你的硬件配置是否可以支持并行(如是否有多个CPU,多个硬盘控制器),所以要具体问题具体对待。
Filter
filter 的操作是对外表的每一行,都要对内表执行一次全表扫描,他其实很像我们熟悉的neested loop,但它的独特之处在于会维护一个hash table。其实filter的性能实际上跟列值distinct数有关,oracle在执行的时候实际上做了很大优化,最坏情况下才会出现对外表每一行 执行一次filter操作,如果distinct值比较少,那执行效率还是非常高的,甚至有可能比nl更高。
通过下面的例子来具体解释Filter和Nest loop的区别。
SQL> explain plan for select tabs. from tabs where  exists (select /+ no_unnest / cols.table_name from cols where tabs.table_name=cols.table_name) and tabs.owner like 'SC%';
Explained.
SQL> select

from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT

















Plan hash value: 2962517934

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

|   0 | SELECT STATEMENT   |      |     1 |   205 |    17   (0)| 00:00:01 |
|  1 |  FILTER            |      |       |       |            |          |
|
  2 |   TABLE ACCESS FULL| TABS |     5 |  1025 |    12   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| COLS |   577 |  9809 |     2   (0)| 00:00:01 |

PLAN_TABLE_OUTPUT

Predicate Information (identified by operation id):

   1 - filter( EXISTS (SELECT /+ NO_UNNEST / 0 FROM "COLS" "COLS"
              WHERE "COLS"."TABLE_NAME"=:B1))
   2 - filter("TABS"."OWNER" LIKE 'SC%')
   3 - filter("COLS"."TABLE_NAME"=:B1)
Note



   - dynamic sampling used for this statement
22 rows selected.
举例,从tabs的查询结果集中(owner like 'SC%')里取出table_name='A',那么对于colsb表来说即select cols.table_name from cols where cols.table_name='A',如果条件满足,则将tabs表中,tabs.table_name='A'和table_name='A'的结果集存储在hash table里。然后接着从tabs取出table_name='B',如果子查询依旧条件满足,那么将tabs.table_name='B'和table_name='B'的结果集存储在hash table里。接着假设tabs里有重复的table_name,例如我们第三次从取出tabs.table_name='B',那么由于我们对于子查询来说,已经有输入输出对tabs.table_name='B'在hash table里了,ORACLE会非常智能的将tabs.table_name='B'的查询结果作为结果集,而不再对cols表进行全表扫描。这样,filter和neested loop相比,省去了一次全表扫描cols。这个hash table是有大小限制的,当被占满的时候,后续新的tabs.table_name的FILTER就类似neested loop了。
由此可见,当连接字段的distinct value值较小时,FILTER性能优于nested loop。


Hash Join
从Oracle 7.3开始,Hash Join正式进入优化器执行计划生成,只有CBO才能使用Hash Join操作。 在hash join算法中也有驱动表和被驱动表的概念。本质上说,Hash Join连接是借助Hash算法,连带小规模的Nest Loop Join,同时利用内存空间进行高速数据缓存检索的一种算法。
Hash join算法的一个基本思想就是根据小的row sources(称作build input,我们记较小的表为S,较大的表为B) 建立一个可以存在于hash area内存中的hash table,然后用大的row sources(称作probe input) 来探测前面所建的hash table。如果hash area内存不够大,hash table就无法完全存放在hash area内存中。针对这种情况,Oracle在连接键利用一个hash函数将build input和probe input分割成多个不相连的Bucket(分别记作Si和Bi),这个阶段叫做准备阶段;然后各自相应的Bucket,即Si和Bi再做Hash join,这个阶段叫做探测阶段。
如 果在计算完Hash Bucket后,针对某个Bucket所建的hash table还是太大的话,oracle就采用nested-loops hash join。所谓的nested-loops hash join就是对部分Si建立hash table,然后读取所有的Bi与所建的hash table做连接,然后再对剩余的Si建立hash table,再将所有的Bi与所建的hash table做连接,直至所有的Si都连接完了。
Hash Join算法有一个限制,就是它是在假设两张表在连接键上是均匀的,也就是说每个分区拥有差不多的数据。但是实际当中数据都是不均匀的,为了很好地解决这个问题,oracle引进了几种技术,位图向量过滤、角色互换、柱状图。
以下面的例子说明,Hash Join的执行过程如下:
SQL> explain plan for select /+ use_hash(tabs,cols) / from tabs,cols where tabs.table_name=cols.table_name and tabs.owner like 'SC%'
Explained.
SQL> set autotrace off
SQL> select


from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT






Plan hash value: 1399132210

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

|   0 | SELECT STATEMENT   |      |   227 | 66738 |    78   (4)| 00:00:01 |
|  1 |  HASH JOIN         |      |   227 | 66738 |    78   (4)| 00:00:01 |
|
  2 |   TABLE ACCESS FULL| TABS |     5 |  1025 |    12   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| COLS | 57687 |  5013K|    65   (4)| 00:00:01 |

PLAN_TABLE_OUTPUT

Predicate Information (identified by operation id):

   1 - access("TABS"."TABLE_NAME"="COLS"."TABLE_NAME")
   2 - filter("TABS"."OWNER" LIKE 'SC%')
Note

   - dynamic sampling used for this statement
20 rows selected.
 1、计算小表的分区(bucket)数
决 定hash join的一个重要因素是小表的分区(bucket)数。这个数字由hash_area_size、hash_multiblock_io_count和 db_block_size参数共同决定。Oracle会保留hash area的20%来存储分区的头信息、hash位图信息和hash表。因此,这个数字的计算公式是:
Bucket数=0.8hash_area_size/(hash_multiblock_io_countdb_block_size)
2、Hash计算   
根据结果集读取小表tabs数据(简称为R),并对每一条数据根据hash算法进行计算。Oracle采用两种hash算法进行计算,计算出能达到最快速度的hash值
(第一hash值和第二hash值)。而关于这些分区的全部hash值(第一hash值)就成为hash表。
3、存放数据到hash内存中
将经过hash算法计算的数据,根据各个bucket的hash值(第一hash值)分别放入相应的bucket中。第二hash值就存放在各条记录中。
4、创建hash位图
与此同时,也创建了一个关于这两个hash值映射关系的hash位图。
5、超出内存大小部分被移到磁盘
如果hash area被占满,那最大一个分区就会被写到磁盘(临时表空间)上去。任何需要写入到磁盘分区上的记录都会导致磁盘分区被更新。这样的话,就会严重影响性能,因此一定要尽量避免这种情况。
持续2~5步骤,直到整个表的数据读取完毕。
6、对分区排序
为了能充分利用内存,尽量存储更多的分区,Oracle会按照各个分区的大小将他们在内存中排序。
7、读取大表数据,进行hash匹配
接 下来就开始读取大表cols表(简称S)中的数据。按顺序每读取一条记录,计算它的hash值,并检查是否与内存中的分区的hash值一致。如果是,返回 join数据。如果内存中的分区没有符合的,就将S中的数据写入到一个新的分区中,这个分区也采用与计算R一样的算法计算出hash值。也就是说这些S中 的数据产生的新的分区数应该和R的分区集的分区数一样。这些新的分区被存储在磁盘(临时表空间)上。
8、完成大表全部数据的读取
一直按照7进行,直到大表中的所有数据的读取完毕。
9、处理没有join的数据
这个时候就产生了一大堆join好的数据和从R和S中计算存储在磁盘上的分区。
10、二次hash计算
从R和S的分区集中抽取出最小的一个分区,使用第二种hash函数计算出并在内存中创建hash表。采用第二种hash函数的原因是为了使数据分布性更好。
11、二次hash匹配
在从另一个数据源(与hash在内存的那个分区所属数据源不同的)中读取分区数据,与内存中的新hash表进行匹配。返回join数据。
12、完成全部hash join
继续按照9-11处理剩余分区,直到全部处理完毕。
注 意:hash算法中的位图包含了每个hash分区是否有值的信息。它记录了有数据的分区的hash值。这个位图的最大作用就是,如果S表中的数据没有与内 存中的hash表匹配上,先查看这个位图,已决定是否将没有匹配的数据写入磁盘。那些不可能匹配到的数据(即位图上对应的分区没有数据)就不再写入磁盘。
Hash Join的相关参数
HASH_JOIN_ENABLED
这个参数是控制查询计划是否采用hash join的“总开关”。它可以在会话级和实例级被修改。默认为TRUE,既可以(不是一定,要看优化器计算出来的代价)使用。如果设为FALSE,则禁止使用hash join。
HASH_AREA_SIZE
这 个参数控制每个会话的hash内存空间有多大。它也可以在会话级和实例级被修改。默认(也是推荐)值是sort area空间大小的两倍(2SORT_AREA_SIZE)。要提高hash join的效率,就一定尽量保证sort area足够大,能容纳下整个小表的数据。但是因为每个会话都会开辟一个这么大的内存空间作为hash内存,所以不能过大(一般不建议超过2M)。在 Oracle9i及以后版本中,Oracle不推荐在dedicated server中使用这个参数来设置hash内存,而是推荐通过设置PGA_AGGRATE_TARGET参数来自动管理PGA内存。保留 HASH_AREA_SIZE只是为了向后兼容。在dedicated server中,hash area是从PGA中分配的,而在MTS(Multi-Threaded Server)中,hash area是从UGA中分配的。另外,还要注意的是,每个会话并不一定只打开一个hash area,因为一个查询中可能不止一个hash join,这是就会相应同时打开多个hash area。
HAHS_MULTIBLOCK_IO_COUNT
这 个参数决定每次读入hash area的数据块数量。因此它会对IO性能产生影响。他只能在init.ora或spfile中修改。在8.0及之前版本,它的默认值是1,在8i及以后 版本,默认值是0。一般设置为1-(65536/DB_BLOCK_SIZE)。在9i中,这个参数是一个隐藏参 数:_HASH_MULTIBLOCK_IO_COUNT,可以通过表x$ksppi查询和修改。另外,在MTS中,这个参数将不起作用(只会使用1)。 它的最大值受到OS的IO带宽和DB_BLOCK_SIZE的影响。既不能大于MAX_IO_SIZE/DB_BLOCK_SIZE。在8i及以后版本, 如果这个值设置为0,则表示在每次查询时,Oracle自己自动计算这个值。这个值对IO性能影响非常大,因此,建议不要修改这个参数,使用默认值0,让 Oracle自己去计算这个值。
如果一定要设置这个值,要保证以下不等式能成立:
     R/M < Po2(M/C)
其中,R表示小表的大小;M=HASH_AREA_SIZE




0.9;Po2(n)为n的2的次方C=HASH_MULTIBLOCK_IO_COUNT*DB_BLOCK_SIZE。
总结
1、Filter
原理上和Nest Loop Join类似,不同点在于维护hash table,总体上性能优于Nest Loop Join。
2、嵌套循环(Nest Loop Join):
对 于被连接的数据子集较小的情况,嵌套循环连接是较好的选择。在嵌套循环中,外表驱动内表,外表返回的每一行都要在内表中检索找到它匹配的行,因此整个查询 返回的结果集不能太大(大于10000不合适),要把返回子集较小的表作为外表(驱动表),而且在内表的连接字段上建议有索引。
3、排序合并连接(Sort Merge Join )
通常情况下哈希连接的效果都比排序合并连接要好。然而如果行源已经被排过序,在执行排序合并连接时不需要再排序了,这时排序归并连接的性能会忧于哈希连接。
4、哈希连接(hash join):
哈希连接是大数据集连接时常用的方式,优化器使用两个表中较小的表,利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。
这种方式适用于较小的表完全可以放入内存的情况,这样成本就是访问两个表的成本之和。但是在表很大的情况下并不能完全放入内存,这时优化器将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段。
本段引用:
Oracle优化规则













































  1. SQL语句全部使用大写, 因为Oracle解析时会把小写全部换成大写再解析

  2. SELECT子句中避免使用“*”

Oracle在解析SQL语句的时候,对于“”将通过查询数据库字典来将其转换成对应的列名。
如果在Select子句中需要列出所有的Column时,建议列出所有的Column名称,而不是简单的用“
”来替代,这样可以减少多于的数据库查询开销。

  1. 减少访问数据库的次数

当执行每条SQL语句时, ORACLE在内部执行了许多工作: 解析SQL语句 > 估算索引的利用率 > 绑定变量 > 读数据块等等

由此可见, 减少访问数据库的次数 , 就能实际上减少ORACLE的工作量。

  1. 整个简单无关联的数据库访问

如果有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系),以减少多于的数据库IO开销。

虽然采取这种方法,效率得到提高,但是程序的可读性大大降低,所以还是要权衡之间的利弊。

  1. 使用Truncate而非Delete

Delete表中记录的时候,Oracle会在Rollback段中保存删除信息以备恢复。Truncate删除表中记录的时候不保存删除信息,不能恢复。因此Truncate删除记录比Delete快,而且占用资源少。
删除表中记录的时候,如果不需要恢复的情况之下应该尽量使用Truncate而不是Delete。
Truncate仅适用于删除全表的记录。

  1. 并不是越多COMMIT就越好

oracle-2中commit 详解
博文转自:http://blog.csdn.net/hzhsan/article/details/9719307

它执行的时候,你不会有什么感觉。commit在数据库编程的时候很常用,当你执行DML操作时,数据库并不会立刻修改表中数据,这时你需要commit,数据库中的数据就立刻修改了,如果在没有commit之前,就算你把整个表中数据都删了,如果rollback的话,数据依然能够还原。听 我这么说,你或许感觉commit没什么用,其实不然。当你同时执行两条或两条以上的sql语句时,问题就出现了。举一个例子,你去银行转账,你转的时候 银行的数据库会update你银行账户里面的数据,同时对另一个人得账户也进行update操作。这两个程序都必须全部正确执行,才能commit,否则 rollback。如果只是完成一条,要么你郁闷,要么银行郁闷,第一种情况是,你的账户的钱没少,转账人得账户上的钱多了,银行郁闷了。第二种情况你的 银行账户的钱少了,他的却没多,你就好郁闷了。Oracle好好学吧!sql不难,plsql努努力也能熬过去,等到优化那,哎!DBA不是那么好当的。 还有就是commit算是显式提交,还有隐式提交,并不是,不commit的话,你的全部努力就都白费了。
这个命令是将数据写到数据库中。如果不执行COMMIT这个命令,那么在你这个session之外的其他session查询的数据是你修改数据之前的数据。而COMMIT之后人家查询的是你修改的数据。你可以打开两个sqlplus比较做一下测试。一目了然。
commit的提交针对的是:DML
Data Manipulation Language(DML) 需要提交,这部分是对数据管理操作,比如Insert(插入)、Update(修改)、Delete(删除),
Data Definition Language(DDL) 不需要提交,这部分是对数据结构定义,比如 Create(创建)、Alter(修改)、Drop(删除)



oracle的commit就是提交数据(这里是释放锁不是锁表),在未提交前你前面的操作更新的都是内存,没有更新到物理文件中。执行commit从用户角度讲就是更新到物理文件了,事实上commit时还没有写date file,而是记录了redo log file,要从内存写到data物理文件,需要触发检查点,由DBWR这个后台进程来写,这里内容有点多的,如果不深究的话你就理解成commit即为从内存更新到物理文件。锁有很多种,一般我们关注的都是DML操作产生的,比如insert,delete,update,select...for update都会同时触发表级锁和行级锁

补充:对的,insert以后commit之前是锁表的状态,其他事务无法对该表进行操作。
如果不提交的话,那么这个表就被锁了
 COMMIT通常是一个非常快的操作,而不论事务大小如何。你可能认为,一个事务越大(换句话说,它影响的数据越多),COMMIT需要的时间就越长。不是这样的。不论事务有多大,COMMIT的响应时间一般都很“平”(flat,可以理解为无高低变化)。这是因为COMMIT并没有太多的工作去做,不过它所做的确实至关重要。
  这一点很重要,之所以要了解并掌握这个事实,原因之一是:这样你就能心无芥蒂地让事务有足够的大小。一种错误的信念认为分批提交可以节省稀有的系统资源,而实际上这只是增加了资源的使用。如果只在必要时才提交(即逻辑工作单元结束时),不仅能提高性能,还能减少对共享资源的竞争(日志文件、各种内部闩等)。


  分批提交COMMIT的开销存在两个因素:

  显然会增加与数据库的往返通信。如果每个记录都提交,生成的往返通信量就会大得多。

  每次提交时,必须等待redo写至磁盘。这会导致“等待”。在这种情况下,等待称为“日志文件同步”(log file sync)。

  为什么COMMIT的响应时间相当“平”,而不论事务大小呢?在数据库中执行COMMIT之前,困难的工作都已经做了。我们已经修改了数据库中的数据,所以99.9%的工作都已经完成。例如,已经发生了以下操作:

  已经在SGA中生成了undo块。

  已经在SGA中生成了已修改数据块。

  已经在SGA中生成了对于前两项的缓存redo。

  取决于前三项的大小,以及这些工作花费的时间,前面的每个数据(或某些数据)可能已经刷新输出到磁盘。

  已经得到了所需的全部锁。

  执行COMMIT时,余下的工作只是:

  为事务生成一个SCN。如果你还不熟悉SCN,起码要知道,SCN是Oracle使用的一种简单的计时机制,用于保证事务的顺序,并支持失败恢复。SCN 还用于保证数据库中的读一致性和检查点。可以把SCN看作一个钟摆,每次有人COMMIT时,SCN都会增1.

  LGWR将所有余下的缓存重做日志条目写到磁盘,并把SCN记录到在线重做日志文件中。这一步就是真正的COMMIT。如果出现了这一步,即已经提交。事务条目会从V$TRANSACTION中“删除”,这说明我们已经提交。

  V$LOCK中记录这我们的会话持有的锁,这些所都将被释放,而排队等待这些锁的每一个人都会被唤醒,可以继续完成他们的工作。

  如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并“清理”。块清除(Block cleanout)是指清除存储在数据库块首部的与锁相关的信息。实质上讲,我们在清除块上的事务信息,这样下一个访问这个块的人就不用再这么做了。我们采用一种无需生成重做日志信息的方式来完成块清除,这样可以省去以后的大量工作(在下面的“块清除”一节中将更全面地讨论这个问题)。

  可以看到,处理COMMIT所要做的工作很少。其中耗时最长的操作要算LGWR执行的活动(一般是这样),因为这些磁盘写是物理磁盘I/O。不过,这里LGWR花费的时间并不会太多,之所以能大幅减少这个操作的时间,原因是LGWR一直在以连续的方式刷新输出重做日志缓冲区的内容。在你工作期间,LGWR并非缓存这你做的所有工作;实际上,随着你的工作的进行,LGWR会在后台增量式地刷新输出重做日志缓冲区的内容。这样做是为了避免COMMIT等待很长时间来一次性刷新输出所有的redo。

  因此,即使我们有一个长时间运行的事务,但在提交之前,它生成的许多缓存重做日志已经刷新输出到磁盘了(而不是全部等到提交时才刷新输出)。这也有不好的一面,COMMIT时,我们必须等待,直到尚未写出的所有缓存redo都已经安全写到磁盘上才行。也就是说,对LGWR的调用是一个同步(synchronous)调用。尽管LGWR本身可以使用异步I/O并行地写至日志文件,但是我们的事务会一直等待LGWR完成所有写操作,并收到数据都已在磁盘上的确认才会返回。

  前面我提高过,由于某种原因,我们用的是一个Java程序而不是PL/SQL,这个原因就是 PL/SQL提供了提交时优化(commit-time optimization)。我说过,LGWR是一个同步调用,我们要等待它完成所有写操作。在Oracle 10g Release 1及以前版本中,除PL/SQL以外的所有编程语言都是如此。PL/SQL引擎不同,要认识到直到PL/SQL例程完成之前,客户并不知道这个PL /SQL例程中是否发生了COMMIT,所以PL/SQL引擎完成的是异步提交。它不会等待LGWR完成;相反,PL/SQL引擎会从COMMIT调用立即返回。不过,等到PL/SQL例程完成,我们从数据库返回客户时,PL/SQL例程则要等待LGWR完成所有尚未完成的COMMIT。因此,如果在PL /SQL中提交了100次,然后返回客户,会发现由于存在这种优化,你只会等待LGWR一次,而不是100次。这是不是说可以在PL/SQL中频繁地提交呢?这是一个很好或者不错的主意吗?不是,绝对不是,在PL/SQ;中频繁地提交与在其他语言中这样做同样糟糕。指导原则是,应该在逻辑工作单元完成时才提交,而不要在此之前草率地提交。

  COMMIT是一个“响应时间很平”的操作,虽然不同的操作将生成不同大小的redo,即使大小相差很大或者说无论生成多少redo,但也并不会影响提交(COMMIT)的时间或者说提交所用的时间都基本相同。

  1. 计算记录条数

Select count(*) from tablename;
Select count(1) from tablename;
Select max(rownum) from tablename;
一般认为,在没有索引的情况之下,第一种方式最快。 如果有索引列,使用索引列当然最快。


  1. 用Where子句替换Having子句

避免使用HAVING子句,HAVING 只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序、总计等操作。 如果能通过WHERE子句限制记录的数目,就能减少这方面的开销。

  1. 减少对表的查询操作

在含有子查询的SQL语句中,要注意减少对表的查询操作。

低效:

复制代码
SELECT TAB_NAME FROM TABLES
WHERE TAB_NAME =(SELECT TAB_NAME
FROM TAB_COLUMNS
WHERE VERSION = 604)
AND DB_VER =(SELECT DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)
复制代码
高效:








SELECT TAB_NAME FROM TABLES
WHERE (TAB_NAME,DB_VER)=
(SELECT TAB_NAME,DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)



  1. 使用表的别名(Alias)

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

Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的归属。

  1. EXISTS和IN的效率比较

综合以上对IN/EXISTS的讨论,我们可以得出一个基本通用的结论:IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大(内表上必须有索引)的情况。

  1. 当连接字段的distinct value值较小时,FILTER性能(EXISTS)优于nested loop(表连接)。

  2. 用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
高效:


SELECT DEPT_NO,DEPT_NAME
FROM DEPT D
WHERE EXISTS (SELECT ‘X’
FROM EMP E
WHERE E.DEPT_NO = D.DEPT_NO



  1. 识别低效的SQL语句

下面的SQL工具可以找出低效SQL :

复制代码
SELECT EXECUTIONS, DISK_READS, BUFFER_GETS,
ROUND ((BUFFER_GETS-DISK_READS)/BUFFER_GETS, 2) Hit_radio,
ROUND (DISK_READS/EXECUTIONS, 2) Reads_per_run,
SQL_TEXT
FROM V$SQLAREA
WHERE EXECUTIONS>0
AND BUFFER_GETS > 0
AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8
ORDER BY 4 DESC
复制代码
另外也可以使用SQL Trace工具来收集正在执行的SQL的性能状态数据,包括解析次数,执行次数,CPU使用时间等 。










  1. 用Explain Plan分析SQL语句

EXPLAIN PLAN 是一个很好的分析SQL语句的工具, 它甚至可以在不执行SQL的情况下分析语句. 通过分析, 我们就可以知道ORACLE是怎么样连接表, 使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称。

  1. SQL PLUS的TRACE

复制代码
SQL> list
1 SELECT
2 FROM dept, emp
3

WHERE emp.deptno = dept.deptno
SQL> set autotrace traceonly /traceonly 可以不显示执行结果/
SQL> /
14 rows selected.
Execution Plan





0 SELECT STATEMENT Optimizer=CHOOSE
1 0 NESTED LOOPS
2 1 TABLE ACCESS (FULL) OF 'EMP'
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
复制代码




  1. 用索引提高效率

(1)特点

优点: 提高效率 主键的唯一性验证

代价: 需要空间存储 定期维护

重构索引:

ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>
(2)Oracle对索引有两种访问模式

索引唯一扫描 (Index Unique Scan)
索引范围扫描 (Index Range Scan)
(3)基础表的选择

基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问)。 根据优化器的不同,SQL语句中基础表的选择是不一样的。
如果你使用的是CBO (COST BASED OPTIMIZER),优化器会检查SQL语句中的每个表的物理大小,索引的状态,然后选用花费最低的执行路径。
如果你用RBO (RULE BASED OPTIMIZER), 并且所有的连接条件都有索引对应,在这种情况下,基础表就是FROM 子句中列在最后的那个表。
(4)多个平等的索引


当SQL语句的执行路径可以使用分布在多个表上的多个索引时,ORACLE会同时使用多个索引并在运行时对它们的记录进行合并,检索出仅对全部索引有效的记录。
在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引。然而这个规则只有当WHERE子句中索引列和常量比较才有效。如果索引列和其他表的索引类相比较。这种子句在优化器中的等级是非常低的。
如果不同表中两个相同等级的索引将被引用,FROM子句中表的顺序将决定哪个会被率先使用。 FROM子句中最后的表的索引将有最高的优先级。
如果相同表中两个相同等级的索引将被引用,WHERE子句中最先被引用的索引将有最高的优先级。
(5)等式比较优先于范围比较



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

即使是唯一性索引,如果做范围比较,其优先级也低于非唯一性索引的等式比较。

(6)不明确的索引等级

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

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

SELECT ENAME
FROM EMP
WHERE DEPTNO > 20
AND EMP_CAT > ‘A’;
这里, ORACLE只用到了DEPT_NO索引. 执行路径如下:



TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON DEPT_IDX

(7)强制索引失效

如果两个或以上索引具有相同的等级,你可以强制命令ORACLE优化器使用其中的一个(通过它,检索出的记录数量少) 。

SELECT ENAME
FROM EMP
WHERE EMPNO = 7935
AND DEPTNO + 0 = 10 /DEPTNO上的索引将失效/
AND EMP_TYPE || ‘’ = ‘A’ /EMP_TYPE上的索引将失效/
(8)避免在索引列上使用计算




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

低效:

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


SELECT …
FROM DEPT
WHERE SAL > 25000/12;
(9)自动选择索引


如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性索引。在这种情况下,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

(10)避免在索引列上使用NOT

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

低效: (这里,不使用索引)

SELECT …
FROM DEPT
WHERE NOT DEPT_CODE = 0
高效:(这里,使用了索引)


SELECT …
FROM DEPT
WHERE DEPT_CODE > 0

  1. 用 >= 替代 >

如果DEPTNO上有一个索引

高效:

SELECT *
FROM EMP
WHERE DEPTNO >=4
低效:


SELECT *
FROM EMP
WHERE DEPTNO >3
两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录,而后者将首先定位到DEPTNO等于3的记录并且向前扫描到第一个DEPT大于3的记录.


  1. 用Union替换OR(适用于索引列)

通常情况下,用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。 注意,以上规则只针对多个索引列有效。

高效:

复制代码
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”

  1. OR / IN / UNION / UNION ALL比较

union查询时,在索引列查询速度快。

or,in采用的是全表扫描机制,更适用于非索引列查找。

union和union all的区别在于,前者在查找后,会对重复数据去重;后者则不会。

  1. 避免在索引列上使用is null和is not null

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

低效:(索引失效)

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


SELECT …
FROM DEPARTMENT
WHERE DEPT_CODE >=0;

  1. 总是使用索引的第一个列

如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时, 优化器才会选择使用该索引。

复制代码
SQL> create index multindex on multiindexusage(inda,indb);
Index created.

SQL> select * from multiindexusage where indb = 1;
Execution Plan

0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (FULL) OF 'MULTIINDEXUSAGE‘
复制代码
很明显, 当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。


  1. 使用UNION ALL替代UNION

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。如果用UNION ALL替代UNION,这样排序就不是必要了,效率就会因此得到提高。

由于UNION ALL的结果没有经过排序,而且不过滤重复的记录,因此是否进行替换需要根据业务需求而定。

  1. 对UNION的优化

由于UNION会对查询结果进行排序,而且过滤重复记录,因此其执行效率没有UNION ALL高。 UNION操作会使用到SORT_AREA_SIZE内存块,因此对这块内存的优化也非常重要。

可以使用下面的SQL来查询排序的消耗量 :

select substr(name,1,25) "Sort Area Name",
substr(value,1,15) "Value"
from v$sysstat
where name like 'sort%'


  1. 避免改变索引列的类型

当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换。

复制代码
/假设EMP_TYPE是一个字符类型的索引列./
SELECT …
FROM EMP
WHERE EMP_TYPE = 123



/这个语句被ORACLE转换为:/
SELECT …
FROM EMP
WHERE TO_NUMBER(EMP_TYPE)=123
复制代码
因为内部发生的类型转换,这个索引将不会被用到。




几点注意:

当比较不同数据类型的数据时,ORACLE自动对列进行简单的类型转换。
如果在索引列上面进行了隐式类型转换,在查询的时候将不会用到索引。
注意当字符和数值比较时,ORACLE会优先转换数值类型到字符类型。
为了避免ORACLE对SQL进行隐式的类型转换,最好把类型转换用显式表现出来。


  1. 使用提示(Hints)

FULL hint 告诉ORACLE使用全表扫描的方式访问指定表。
ROWID hint 告诉ORACLE使用TABLE ACCESS BY ROWID的操作访问表。
CACHE hint 来告诉优化器把查询结果数据保留在SGA中。
INDEX Hint 告诉ORACLE使用基于索引的扫描方式。
其他的Oracle Hints



ALL_ROWS
FIRST_ROWS
RULE
USE_NL
USE_MERGE
USE_HASH 等等。
这是一个很有技巧性的工作。建议只针对特定的,少数的SQL进行hint的优化。





  1. 几种不能使用索引的WHERE子句

(1)下面的例子中,‘!=’ 将不使用索引 ,索引只能告诉你什么存在于表中,而不能告诉你什么不存在于表中。

不使用索引:

SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT !=0;
使用索引:


SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT > 0;
(2)下面的例子中,‘||’是字符连接函数。就象其他函数那样,停用了索引。


不使用索引:

SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME||ACCOUNT_TYPE=’AMEXA’;
使用索引:


SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = ‘AMEX’
AND ACCOUNT_TYPE=’ A’;
(3)下面的例子中,‘+’是数学函数。就象其他数学函数那样,停用了索引。



不使用索引:

SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE AMOUNT + 3000 >5000;
使用索引:


SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE AMOUNT > 2000 ;
(4)下面的例子中,相同的索引列不能互相比较,这将会启用全表扫描。


不使用索引:

SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = NVL(:ACC_NAME, ACCOUNT_NAME)
使用索引:


SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME LIKE NVL(:ACC_NAME, ’%’)

  1. 连接多个扫描

如果对一个列和一组有限的值进行比较,优化器可能执行多次扫描并对结果进行合并连接。

举例:

SELECT *
FROM LODGING
WHERE MANAGER IN (‘BILL GATES’, ’KEN MULLER’)
优化器可能将它转换成以下形式:


SELECT * 
  FROM LODGING
WHERE MANAGER = ‘BILL GATES’
       OR MANAGER = ’KEN MULLER’
  1. CBO下使用更具选择性的索引

基于成本的优化器(CBO,Cost-Based Optimizer)对索引的选择性进行判断来决定索引的使用是否能提高效率。
如果检索数据量超过30%的表中记录数,使用索引将没有显著的效率提高。
在特定情况下,使用索引也许会比全表扫描慢。而通常情况下,使用索引比全表扫描要块几倍乃至几千倍!

  1. 避免使用耗费资源的操作

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作,而其他的至少需要执行两次排序。
通常,带有UNION,MINUS,INTERSECT的SQL语句都可以用其他方式重写。

  1. 优化GROUP BY

提高GROUP BY语句的效率,可以通过将不需要的记录在GROUP BY之前过滤掉。

低效:

SELECT JOB ,AVG(SAL)
FROM EMP
GROUP BY JOB
HAVING JOB = ‘PRESIDENT’
OR JOB = ‘MANAGER’
高效:




SELECT JOB,AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT’
OR JOB = ‘MANAGER’
GROUP BY JOB



  1. 使用日期

当使用日期时,需要注意如果有超过5位小数加到日期上,这个日期会进到下一天!

复制代码
SELECT TO_DATE(‘01-JAN-93’+.99999)
FROM DUAL
Returns:
’01-JAN-93 23:59:59’



SELECT TO_DATE(‘01-JAN-93’+.999999)
FROM DUAL
Returns:
’02-JAN-93 00:00:00’
复制代码



  1. 使用显示游标(CURSORS)

使用隐式的游标,将会执行两次操作。第一次检索记录,第二次检查TOO MANY ROWS 这个exception。而显式游标不执行第二次操作。

  1. 分离表和索引

总是将你的表和索引建立在不同的表空间内(TABLESPACES)。
决不要将不属于ORACLE内部系统的对象存放到SYSTEM表空间里。
确保数据表空间和索引表空间置于不同的硬盘上。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!