• [转帖 作者: fuyuncat 来源: www.HelloDBA.com ]Oracle IO问题解析


    数据库的作用就是实现对数据的管理和查询。任何一个数据库系统,必然存在对数据的大量读或者写或者两中操作都大量存在。IO问题也往往是导致数据库性能问题的重要原因。在这篇文章中,主要帮助大家在理解Oracle的读写操作机制的基础上,灵活解决遇到的各种常见的IO问题。

    1         Oracle中IO的产生

    IO当然包括了读、写两部分,先介绍Oracle中写操作的产生。

    1.1      写

    介绍写操作之前,先简单的看下Oracle的物理结构:oracle的物理文件包括以下三种文件:控制文件(Control Files)、重做日志文件(Redo Log Files)、数据文件(datafiles)。而数据文件中,根据功能的不同,还可以分为系统数据文件、临时空间文件、回滚段文件和用户数据文件。另外,如果数据库的Archive Log模式被激活,还存在归档日志文件。Oracle的IO产生,就是对这些文件的数据读、写操作。下面再详细看下几种主要写操作的产生及其过程。

    1.1.1      控制文件

    控制文件中记录了整个数据库的物理结构信息,如数据库名字、数据文件及日志文件名字和位置、事件戳信息等等。任何数据库的结构变化(如果创建新的数据文件)都会引起Oracle修改控制文件。同时控制文件还记录系统和各个数据文件的SCN(System Change Number,关于SCN可以参见文章《Oracle SCN机制详解》)信息,以用于数据恢复,因此数据文件上的SCN变化后,Oracle也会相应修改控制文件上的SCN信息。

    1.1.2      用户数据修改

    由于内存的读写效率比磁盘的读写效率高万倍,因此,为了降低IO wait,oracle会将数据cache在内存(Buffer Cache,对Buffer Cache的详细介绍可以参见《Oracle内存全面分析》)中,对数据的读写尽量在内存中完成。当Buffer Cache中的数据缓存块被修改过了,它就被标记为“脏”数据。根据LRU(Least Recently Used)算法,如果一个数据块最近很少被使用,它就称为“冷”数据块。进程DBWn(系统中可以存在多个DBW进程,n为序号)负责将“冷”的“脏”数据写入数据文件中去。DBWn进程会在以下两种情况下将“脏”数据写入磁盘中去:

    • 当服务进程扫描一定数量(阀值)的Buffer Cache后还没有找到干净、可重用的缓存块后,它会通知DBWn进程将“脏”数据写入文件中去,以释放出空闲缓存;
    • 当发生检查点(Checkpoint)时。

    1.1.3      Redo Log

    在非直接写(Direct Write)的情况下,事务中的写操作都会产生Redo Log,作为数据块异常关闭时的恢复记录。同样,和写用户数据类似,Redo Log也不会被直接写入Redo Log文件,而是先写入Log Buffer中。

    Log Buffer是一个可以循环重用的缓存区。LGWR进程负责将Log Buffer中的记录写入Redo Log File中去。一旦Log Buffer中的条目被写入了Redo Log文件中,就可以被重用了。

    为了保证事务尽快获得Log Buffer,LGWR进程一般会尽快将Log Buffer中的数据写入Redo Log文件中去。在以下几种情况下,LGWR回将一个连续的Log Buffer写入Redo Log文件中去:

    • 当一个事务提交(COMMIT)时;
    • 每3秒钟写一次Log Buffer;
    • 当Log Buffer到达1/3满时;
    • 当DBWn进程将“脏”数据写入磁盘时;

    1.1.4      Archive Log

    当据库的Archive Log模式被激活后,所有Redo Log数据都会被写入Archive Log文件中以便日后进行恢复。当发生日志组切换时,ARCn(Archive进程,可以存在多个)进程就会Redo Log文件拷贝到指定存储目录中去,成为Archive Log文件。

    1.1.5      临时表空间

    当Oracle在执行一些SQL时,会需要一些临时空间来存储执行语句时产生的中间数据。这些临时空间由Oracle从指定的临时表空间中分配给进程。主要有三种情况会占用临时空间:临时表/索引操作、排序和临时LOB操作。

    • 临时表/索引

    在会话中,当第一次对临时表进行INSERT(包括CTAS)时,Oracle会从临时表空间中为临时表及其索引分配临时空间一存储数据。

    • 排序

    任何会使用到排序的操作,包括JOIN、创建(重建)INDEX、ORDER BY、聚合计算(GROUP BY)以及统计数据收集,都可能使用到临时表空间。

    排序操作首先会选择在内存中的Sort Area进行(Sort In Memory),一旦Sort Area不足,则会使用临时空间进行排序操作(Sort In Disk)。看以下例子:

    SQL> alter session set sort_area_size = 10000000;
     
    Session altered.
     
    SQL> select owner, object_name from t_test1
      2  order by object_id;
     
    47582 rows selected.
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1312425564
     
    ------------------------------------------------------------------------------
    | Id  | Operation          | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |         | 47582 |  1486K|   155   (4)| 00:00:02 |
    |   1 |  SORT ORDER BY     |         | 47582 |  1486K|   155   (4)| 00:00:02 |
    |   2 |   TABLE ACCESS FULL| T_TEST1 | 47582 |  1486K|   150   (1)| 00:00:02 |
    ------------------------------------------------------------------------------
     
     
    Statistics
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
            658  consistent gets
              0  physical reads
              0  redo size
        1566184  bytes sent via SQL*Net to client
          35277  bytes received via SQL*Net from client
           3174  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
          47582  rows processed
     
    SQL> alter session set sort_area_size = 10000;
     
    Session altered.
     
    SQL> select owner, object_name from t_test1
      2  order by object_id;
     
    47582 rows selected.
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1312425564
     
    --------------------------------------------------------------------------------
    | Id  | Operation          | Name    | Rows  | Bytes |TempSpc| Cost (%CPU)| Time|
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |         | 47582 |  1486K|       |  1251   (1)| 00:0
    0:16 |
    |   1 |  SORT ORDER BY     |         | 47582 |  1486K|  4136K|  1251   (1)| 00:0
    0:16 |
    |   2 |   TABLE ACCESS FULL| T_TEST1 | 47582 |  1486K|       |   150   (1)| 00:0
    0:02 |
     
    ---------------------------------------------------------------------------------
     
    Statistics
    ----------------------------------------------------------
              6  recursive calls
             20  db block gets
            658  consistent gets
            629  physical reads
              0  redo size
        1566184  bytes sent via SQL*Net to client
          35277  bytes received via SQL*Net from client
           3174  SQL*Net roundtrips to/from client
              0  sorts (memory)
              1  sorts (disk)
          47582  rows processed
    • 临时LOB对象

    LOB对象包括BLOB、CLOB、NCLOB、和BFILE。在PLSQL程序块中,如果定义了LOB变量,则这些LOB变量就是临时LOB对象。临时LOB对象被创建在临时表空间上,直到LOB数据被释放,或者会话结束。

    1.1.6      回滚段

    我们知道,一个事务在未被提交前,其做的任何修改都是可以被回滚(Rollback)的。这些回滚数据就被放到回滚段(Rollback Segment)上。此外,一致性读(Read Consistency)、数据库恢复(Recover)都会用到回滚段。

    任何数据块的修改都会被记录在回滚段中,甚至Redo Log也会产生回滚记录。当任何一个非只读(只有查询)的事务开始时,oracle会自动为其指定下一个可用的回滚段。事务中任何数据变化都被写入回滚段中。如果事务回滚,oracle根据回滚段中的回滚记录将buffer cache中的“脏”数据恢复,释放回滚段空间。当事务被提交,由于要保证一致性读,oracle并不会立即释放回滚段中的数据,而是会保留一段时间。

    1.1.7      Direct-Path Insert

    这里,我们还要介绍一种特殊的写操作——Direct-Path Insert(直接路径插入)。Direct-Path Insert通过直接在表中已存在的数据后面添加数据,直接将数据写入数据文件中,而忽略掉了Buffer Cache。

    我们前面提到,为了能在意外时恢复数据,每一个数据修改都会被记录到Redo Log中。然而,由于Redo Log需要写入到物理文件中去,是一个比较消耗性能的操作。为了提高性能,我们在批量写入数据时就可以通过Direct-Path Insert的指定NOLOGING的方式来避免写Redo Log。

    有多种方法可以指定Direct-Path Insert:CTAS(CREATE TABLE AS SELECT);SQL*Loader指定Direct参数;在语句中指定APPEND提示。

    1.2     读

    1.2.1      物理读

    产生物理读主要有以下几种情况:

    • 第一次读取

    当数据块第一次被读取到,Oracle会先将其从磁盘上读入Buffer Cache中,并将他们放在LRU(Last Recently Used)链表的MRU(Most Recently Used)端。再次访问数据块时就可以直接从Buffer Cache中读取、修改了。看以下例子:

    SQL> select owner, index_name from t_test3;
     
    2856 rows selected.
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 2878488296
     
    -----------------------------------------------------------------------------
    | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |         |  2856 | 68544 |    22   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T_TEST3 |  2856 | 68544 |    22   (0)| 00:00:01 |
    -----------------------------------------------------------------------------
     
     
    Statistics
    ----------------------------------------------------------
            407  recursive calls
             32  db block gets
            344  consistent gets
             89  physical reads
              0  redo size
         103888  bytes sent via SQL*Net to client
           2475  bytes received via SQL*Net from client
            192  SQL*Net roundtrips to/from client
              9  sorts (memory)
              0  sorts (disk)
           2856  rows processed
     
    SQL> select owner, index_name from t_test3;
     
    2856 rows selected.
     
    Elapsed: 00:00:00.03
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 2878488296
     
    -----------------------------------------------------------------------------
    | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |         |  2856 | 68544 |    22   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T_TEST3 |  2856 | 68544 |    22   (0)| 00:00:01 |
    -----------------------------------------------------------------------------
     
     
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            276  consistent gets
              0  physical reads
              0  redo size
         103888  bytes sent via SQL*Net to client
           2475  bytes received via SQL*Net from client
            192  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
           2856  rows processed
    • 数据块被重新读入Buffer Cache

    如果有新的数据需要被读入Buffer Cache中,而Buffer Cache又没有足够的空闲空间,Oracle就根据LRU算法将LRU链表中LRU端的数据置换出去。当这些数据被再次访问到时,需要重新从磁盘读入。

    SQL> select owner, table_name from t_test2
      2  where owner = 'SYS';
     
    718 rows selected.
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1900296288
    --------------------------------------------------------------------------------
    | Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)
    | Time     |
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |              |    99 |  2178 |    10   (0)
    | 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST2      |    99 |  2178 |    10   (0)
    | 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | T_TEST2_IDX1 |    99 |       |     1   (0)
    | 00:00:01 |
    --------------------------------------------------------------------------------
     
     
    Predicate Information (identified by operation id):
    ---------------------------------------------------
     
       2 - access("OWNER"='SYS')
     
     
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            145  consistent gets
              0  physical reads
              0  redo size
          21690  bytes sent via SQL*Net to client
            902  bytes received via SQL*Net from client
             49  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
            718  rows processed
     
    SQL> select * from t_test1; --占用Buffer Cache
     
    47582 rows selected.
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1883417357
     
    -----------------------------------------------------------------------------
    | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |         | 47582 |  3996K|   151   (2)| 00:00:02 |
    |   1 |  TABLE ACCESS FULL| T_TEST1 | 47582 |  3996K|   151   (2)| 00:00:02 |
    -----------------------------------------------------------------------------
     
    Statistics
    ----------------------------------------------------------
            195  recursive calls
              0  db block gets
           3835  consistent gets
              5  physical reads
              0  redo size
        5102247  bytes sent via SQL*Net to client
          35277  bytes received via SQL*Net from client
           3174  SQL*Net roundtrips to/from client
              5  sorts (memory)
              0  sorts (disk)
          47582  rows processed
     
    SQL> select owner, table_name from t_test2
      2  where owner = 'SYS';
     
    718 rows selected.
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1900296288
     
    --------------------------------------------------------------------------------
    | Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)
    | Time     |
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |              |    99 |  2178 |    10   (0)
    | 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST2      |    99 |  2178 |    10   (0)
    | 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | T_TEST2_IDX1 |    99 |       |     1   (0)
    | 00:00:01 |
    --------------------------------------------------------------------------------
     
     
    Predicate Information (identified by operation id):
    ---------------------------------------------------
     
       2 - access("OWNER"='SYS')
     
     
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            145  consistent gets
             54  physical reads
              0  redo size
          21690  bytes sent via SQL*Net to client
            902  bytes received via SQL*Net from client
             49  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
            718  rows processed
    • 全表扫描

    当发生全表扫描(Full Table Scan)时,用户进程读取表的数据块,并将他们放在LRU链表的LRU端(和上面不同,不是放在MRU端)。这样做的目的是为了使全表扫描的数据尽快被移出。因为全表扫描一般发生的频率较低,并且全表扫描的数据块大部分在以后都不会被经常使用到。

    而如果你希望全表扫描的数据能被cache住,使之在扫描时放在MRU端,可以通过在创建或修改表(或簇)时,指定CACHE参数。

    1.2.2      逻辑读

    逻辑读指的就是从(或者视图从)Buffer Cache中读取数据块。按照访问数据块的模式不同,可以分为即时读(Current Read)和一致性读(Consistent Read)。注意:逻辑IO只有逻辑读,没有逻辑写。

    • 即时读

    即时读即读取数据块当前的最新数据。任何时候在Buffer Cache中都只有一份当前数据块。即时读通常发生在对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。

    SQL> select * from t_test1 where owner='SYS' for update;
     
    22858 rows selected.
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3323170753
     
    ------------------------------------------------------------------------------
    | Id  | Operation          | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |         | 22858 |  1919K|   151   (2)| 00:00:02 |
    |   1 |  FOR UPDATE        |         |       |       |            |          |
    |*  2 |   TABLE ACCESS FULL| T_TEST1 | 22858 |  1919K|   151   (2)| 00:00:02 |
    ------------------------------------------------------------------------------
     
    Predicate Information (identified by operation id):
    ---------------------------------------------------
     
       2 - filter("OWNER"='SYS')
     
     
    Statistics
    ----------------------------------------------------------
             44  recursive calls
          23386  db block gets
           2833  consistent gets
              0  physical reads
        5044956  redo size
        2029221  bytes sent via SQL*Net to client
          17138  bytes received via SQL*Net from client
           1525  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
          22858  rows processed
    • 一致性读

    Oracle是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改它将要读取的数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在Buffer Cache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新Buffer Cache中的数据块的数据及其SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己的SCN。如果数据块上的SCN小于等于进程本身的SCN,则直接读取数据块上的数据;如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。

    下面这个例子帮助大家理解一下一致性读:

    会话1中:

    SQL> select object_name from t_test1 where object_id = 66;
     
    OBJECT_NAME
    ------------------------------
    I_SUPEROBJ1
     
    SQL> update t_test1 set object_name = 'TEST' where object_id = 66;
     
    1 row updated.

    会话2中:

    SQL> select object_name from t_test1 where object_id = 66;
     
    OBJECT_NAME
    ------------------------------
    I_SUPEROBJ1
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1883417357
     
    -----------------------------------------------------------------------------
    | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |         |     1 |    27 |   151   (2)| 00:00:02 |
    |*  1 |  TABLE ACCESS FULL| T_TEST1 |     1 |    27 |   151   (2)| 00:00:02 |
    -----------------------------------------------------------------------------
     
    Predicate Information (identified by operation id):
    ---------------------------------------------------
     
       1 - filter("OBJECT_ID"=66)
     
     
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            661  consistent gets
              0  physical reads
            108  redo size
            423  bytes sent via SQL*Net to client
            385  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    1.2.3      查找数据

    在一个查询操作中,大量的读操作都产生于数据的查找过程中。减少查找过程是我们优化IO性能问题的重要目标。

    下面介绍几种主要的数据查找方式。

    • Full Table Scan

    当查询条件无法命中任何索引、或者扫描索引的代价大于全表扫描代价的某一比例时(由参数optimizer_index_cost_adj设定),Oracle会采用全表扫描的方式查找数据。当发生全表扫描时,Oracle会自下向上一次读取一定数量(由参数db_file_multiblock_read_count设定)的数据块,一直读取到高水位标志(HWM,High Water Mark)下。Full Table Scan会引起db file scattered read事件。

    • INDEX UNIQUE SCAN

    全表扫描查找数据的效率是非常低的。而索引能大幅提高查找效率。普通索引的数据结构是B-Tree,树的叶子节点中包含数据的ROWID,指向数据记录,同时还有指针指向前一个/后一个叶子节点。索引扫描每次读取一个数据块,索引扫描是“连续的”(Sequential)。当索引为UNIQUE索引时,每个叶子节点只会指向一条数据。如果Oracle能预知扫描结果只有0或1条记录时,会采用INDEX UNIQUE SCAN。当对Unique Index中的所有字段进行完全匹配时,会发生INDEX UNIQUE SCAN。

    SQL> select object_name from t_test1
      2  where object_id = 66;
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 2634232531
     
    ---------------------------------------------------------------------------------
    | Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)|
    Time     |
    ---------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |            |     1 |    27 |     1   (0)|
    00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1    |     1 |    27 |     1   (0)|
    00:00:01 |
    |*  2 |   INDEX UNIQUE SCAN         | T_TEST1_PK |     1 |       |     1   (0)|
    00:00:01 |
    ---------------------------------------------------------------------------------

    INDEX UNIQUE SCAN的查找过程如下:

    1. 从数的根节点数据块开始查找;
    2. 查找根节点块中所有key值中大于或等于要查找的值的最小key值;
    3. 如果key值大于查找值,则继续查找这个key值之前一个key值所指向的子节点数据块;
    4. 如果key值等于查找值,则继续查找这个key值所指向的子节点数据块;
    5. 如果没有key值大于或等于查找值,则继续查找最大key值所指向的子节点数据块;
    6. 如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
    7. 如果查找的节点是叶子节点数据块,则在数据块中查找等于查找值的key值;
    8. 如果找到相等的key值,则返回数据和ROWID;
    9. 如果没找到相等的key值,则说明没有符合条件的数据,返回NULL。
    • INDEX RANGE SCAN

    如果通过索引查找数据时,Oracle认为会返回数据可能会大于1,会进行INDEX RANGE SCAN,例如Unique Index中字段不完全匹配查找时、非Unique Index查找时。

    SQL> select object_name from t_test1
      2  where object_id < 66;
     
    64 rows selected.
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1635545337
     
    ---------------------------------------------------------------------------------
    | Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)|
    Time     |
    ---------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |            |    57 |  1539 |     2   (0)|
    00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1    |    57 |  1539 |     2   (0)|
    00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | T_TEST1_PK |    57 |       |     1   (0)|
    00:00:01 |
    ---------------------------------------------------------------------------------

    INDEX RANGE SCAN分为闭包(有前后查找边界)和非闭包(只有一边或者没有边界)。返回数据会依据索引增序排序,多个相同值则会按照ROWID的增序排序。以下的查找条件都是闭包的:

    WHERE column = 'Value'
    WHERE column like 'value%'
    WHERE column between 'value1' and 'value2'
    WHERE column in ('value1', 'value2')

    以下查找条件非闭包:

    WHERE column < 'value1'
    WHERE column > 'value2'

    闭包条件下的INDEX RANGE SCAN的查找过程如下:

    1. 从数的根节点数据块开始查找;
    2. 查找根节点块中所有key值中大于或等于要查找的起始值的最小key值;
    3. 如果key值大于起始值,则继续查找这个key值之前一个key值所指向的子节点数据块;
    4. 如果key值等于起始值,则继续查找这个key值所指向的子节点数据块;
    5. 如果没有key值大于或等于起始值,则继续查找最大key值所指向的子节点数据块;
    6. 如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
    7. 如果查找的节点是叶子节点数据块,则在数据块中大于或等于要查找的起始值的最小key值;
    8. 如果Key值小于或等于结束值,则:如果所有Key字段都符合WHERE字句中的查找条件,则返回数据和ROWID;否则继续查找当前叶子节点所指向的右边的叶子节点。

    INDEX UNIQUE SCAN和INDEX RANGE SCAN都会引起db file sequential read事件。

    • TABLE ACCESS BY INDEX ROWID

    当发生索引扫描时,如果需要返回的字段都在索引上,则直接返回索引上的数据,而如果还需要返回非索引上的字段的值,Oracle则需要根据从索引上查找的ROWID到对应的数据块上取回数据,这时就是TABLE ACCESS BY INDEX ROWID。

    • INDEX FAST FULL SCAN & INDEX FULL SCAN

    索引快速全扫描和全表扫描类似,一次读取db_file_multiblock_read_count个数据块来描所有索引的叶子节点。INDEX FAST FULL SCAN和其他索引扫描不同,它不会从树的根节点开始读取,而是直接扫描所有叶子节点;也不会一次读取一个数据块,而是一次读取db_file_multiblock_read_count个数据块。INDEX FAST FULL SCAN会引起db file scattered read事件。

    SQL> select count(1) from t_test1 where object_id < 21314;
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1586700957
     
    ---------------------------------------------------------------------------------
    | Id  | Operation             | Name       | Rows | Bytes| Cost (%CPU)| Time   |
    ---------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |            |     1|     4|    24   (5)| 00:00:01|
    |   1 |  SORT AGGREGATE       |            |     1|     4|            |         |
    |*  2 |   INDEX FAST FULL SCAN| T_TEST1_PK | 18264| 73056|    24   (5)| 00:00:01|
    ---------------------------------------------------------------------------------

    在某些情况下,如db_file_multiblock_read_count值过小、强制使用索引扫描时,会发生INDEX FULL SCAN。INDEX FULL SCAN和INDEX FAST FULL SCAN不同,它是一种索引扫描,按照B-Tree的查找法从树的根节点开始扫描,遍历整棵树,并且一次读取一个数据块。它会引起db file sequential read事件。

    SQL> select /*+index(a t_test1_pk)*/count(1) from t_test1 a;
     
     
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 138350774
     
    -----------------------------------------------------------------------
    | Id  | Operation        | Name       | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------
    |   0 | SELECT STATEMENT |            |     1 |    61   (2)| 00:00:01 |
    |   1 |  SORT AGGREGATE  |            |     1 |            |          |
    |   2 |   INDEX FULL SCAN| T_TEST1_PK | 47582 |    61   (2)| 00:00:01 |
    -----------------------------------------------------------------------

  • 相关阅读:
    二、一切都是对象
    一、对象导论
    CSS 属性大全
    CSS颜色代码大全
    CSS Position 定位属性
    CSS Box Model 盒子模型
    ThreadLocal
    Java 模拟死锁
    byte 最小值为啥是最小是 -128 ;int最小值为啥是 -2147483648
    cmd 查看端口号占用情况
  • 原文地址:https://www.cnblogs.com/adu1218/p/2534197.html
Copyright © 2020-2023  润新知