亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Oracle中IO

系統 2348 0

數據庫的作用就是實現對數據的管理和查詢。任何一個數據庫系統,必然存在對數據的大量讀或者寫或者兩中操作都大量存在。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 |
    
  
    
      -----------------------------------------------------------------------
    
  

2 IO系統的設計和配置

要控制好數據庫的整體IO性能,在規劃數據庫架構時就需要做好IO系統的設計和配置。例如,將對IO要求不同的文件放置在不同的存儲設備上;規劃數據文件的分布、均衡IO負擔等。

2.1 OS和存儲相關

IO性能是直接和操作系統已經硬件性能相關的。如果能利用操作系統的一些高級IO特性,或者采用更高速的磁盤設備,能大大提高IO性能。下面介紹一些OS的IO配置、不同的磁盤硬件設備以及存儲技術。

2.1.1 文件系統(File System)和裸設備(Raw Device)

我們知道,內存的讀寫效率比磁盤高近萬倍,因此Oracle在內存中開辟了一片區域,稱為Buffer Cache,使數據的讀寫盡量在Buffer Cache中完成。同樣,在文件系統中,操作系統為了提高讀寫效率,也會為文件系統開辟一塊Buffer Cache用于讀寫數據的緩存。這樣,Oracle的數據會被緩存2次。為了避免OS的這次緩存,我們可以采用裸設備做為數據文件的存儲設備。裸設備,也稱為裸分區(Raw Partition),它是一個沒有被加載(Mount)到操作系統的文件系統上、也沒有加載到Oracle集群文件系統(OCFS Oracle Cluster File System)的磁盤分區,它通過字符設備驅動來訪問。裸設備的文件讀寫不由操作系統控制,而是由應用程序(如Oracle RDBMS)直接控制。

2.1.2 IO方式

OS和文件系統對IO的控制存在多種方式,不同的IO方式下對于數據庫的IO性能影響也不同。

2.1.2.1 Direct IO & Concurrent IO

除了裸設備,某些文件系統可以支持Direct IO,以避開讀寫緩沖。如果要使用Direct IO,需要指定Oracle參數“filesystemio_options”來設置支持Direct IO。但是要注意,不同OS中的不同文件系統對Direct IO的支持也不同:

  • Windows 在windows中不需要做特別設置可以直接使用Direct IO;
  • AIX 在AIX中,JFS文件系統需要通過設置“filesystemio_options”為“SETALL”或者“DIRECTIO”來支持Direct IO;
  • LINUX Linux在內核版本為2.4.9以上才支持Direct IO。NFS或者OCFS文件系統支持Direct IO。需要設置“filesystemio_options”為“SETALL”或者“DIRECTIO”;
  • Solaris Solaris需要在操作系統中設置“forcedirectio”選項,并設置“filesystemio_options”為“SETALL”或者“DIRECTIO”。

參數“filesystemio_options”支持4種值:

  • ASYNCH: 使Oracle支持文件的異步(Asynchronous)IO;
  • DIRECTIO:使Oracle支持文件的Direct IO;
  • SETALL:使Oracle同時支持文件的Asynchronous IO和Direct IO;
  • NONE:使Oracle關閉對Asynchronous IO和Direct IO的支持。

在AIX的JFS2文件系統上,如果“filesystemio_options”為“SETALL”,則會支持Concurrent IO。CIO比DIO的性能更高,因為JFS2的CIO支持多個進程同時對一個文件進行讀寫。

2.1.2.2 Asynchronous IO & Synchronous IO

通常,用的比較多的IO模型是同步IO(Synchronous IO)。在這種模式下,當請求發出之后,應用程序就會阻塞,直到請求滿足為止。這種模式最大好處就是調用應用程序在等待 I/O 請求完成時不需要使用CPU資源。但是,對于一些強調高響應速度的程序(如DB)來說,希望這種等待時間越短越好,我們這時就可以考慮采用異步IO(Asynchronous IO)模式。異步IO模式下,進程發出IO請求后無需等待IO完成,可以去處理其它事情;IO請求被放入一個隊列中,一旦IO完成,系統會發出信號通知進程。

異步IO可以使需要大量寫的Oracle進程(如DBWn進程)將IO請求隊列化,以充分利用硬件的IO帶寬,從而使它們能最大程度實現并行處理。異步IO還可以使那些需要進行大量計算的操作(如排序)在它們發出IO請求前預先從磁盤取出數據,以使IO和計算并行處理。

確認操作系統已經設置支持AIO后,還需要設置Oracle初始化參數"DISK_ASYNCH_IO"為“true”以支持異步IO。

2.1.3 負載均衡及條帶化(Striping)

當多個進程同時訪問一個磁盤時,會出現磁盤沖突。大多數磁盤系統都對訪問次數(每秒的IO操作)和數據傳輸率(每秒傳輸的數據量)有限制。當達到這些限制時,后面要訪問磁盤的進程就需要等待,這時就是所謂的磁盤沖突。

避免磁盤沖突是優化IO性能的一個目標,這就需要將一個熱點磁盤上的IO訪問負載分擔到其他可用磁盤上,也就是IO負載均衡。在一些成熟的磁盤負載均衡技術出現之前,DBA需要了解/預測各系統的IO負載量,通過手工配置每個數據到不同存放位置以分擔IO負載來達到負載均衡的目的。

條帶化技術就是將數據分成很多小部分并把他們分別存儲到不同磁盤上的不同文件中去。這就能使多個進程同時訪問數據的多個不同部分而不會造成磁盤沖突。很多操作系統、磁盤設備供應商、各種第三方軟件都能做到條帶化。通過條帶化,DBA可以很輕松的做到IO負載均衡而無需去手工配置。

2.1.4 RAID

RAID的全稱是獨立磁盤冗余陣列(Redundant Array of Independent Disks)。它通過將多個相對比較便宜的磁盤組合起來,并相互連接,同時都連到一個或多個計算機上,以組成一個磁盤組,使其性能和容量達到或超過一個價格更昂貴的大型磁盤。RAID分為6級。

  • RAID-0

RAID-0只提供純粹的條帶化(Stripping)。條帶可以使一個大文件被多個磁盤控制器同時訪問,因此支持對數據的并發訪問。RAID-0不提供數據冗余和奇偶保護,它只關注性能。如果RAID-0中任何一個磁盤出錯,整個數據庫都會崩潰。

  • RAID-1

RAID-1提供磁盤鏡像(Disk Mirror)。在RAID-1中,所有數據都會被寫入兩個獨立的磁盤中,以實現對數據的冗余保護。兩塊磁盤的數據是同時寫入的,以保證其速度不會低于寫入單獨磁盤的速度。RAID-1實現了數據的完全冗余,它提供了所有RAID級別中最安全可靠的數據保護。在這種模式下,寫的性能下降了,但讀的性能被提升了。此外,RAID-1也是最占用磁盤空間的模式

  • RAID 0+1

RAID-0能提供更好的性能,RAID-1提供最佳的數據保護。如果把兩者結合在一起就能同時提供高性能和數據保護,但是也會同時提高磁盤陣列造價。

  • RAID-3

在RAID-3中,會有一塊專門的磁盤驅動被用作存儲錯誤修正或者奇偶校驗數據。而其他的磁盤驅動則被條帶化。RAID-3的并行處理能力比較低,它適合于主要是讀操作的系統(如決策分析系統 DSS,但是DSS會存在大量復雜查詢,需要做JOIN,同樣也會存在一些臨時的寫操作),不適合存在大量寫操作的系統(OLTP)。

  • RAID-5

RAID-5不做全磁盤鏡像,但它會對每一個寫操作做奇偶校驗計算并寫入奇偶校驗數據。奇偶校驗磁盤避免了像RAID-1那樣完全重復寫數據。當一個磁盤失效,校驗數據被用來重建數據,從而保證系統不會崩潰。為避免磁盤瓶頸,奇偶校驗和數據都會被分布到陣列中的各個磁盤。盡管讀的效率提高了,但是RAID-5需要為每個寫操作做奇偶校驗,因此它的寫的效率很差。

  • RAID-S

RAID-S是EMC公司的RAID-5的實施方案,它和純粹的RAID-5存在以下區別:

(1) 它條帶化奇偶校驗,但不條帶化數據;

(2) 它與一個帶有寫緩存的異步硬件環境合并。

這個緩存主要是一種延遲寫的機制,因此它能讓系統在相對不忙的時候計算和寫奇偶校驗信息。

  • RAID-7

RAID-7也同樣引入了緩存機制,這個緩存是被一個內嵌式操作系統控制。但是,RAID-7中數據是被條帶化的,而奇偶校驗不被條帶化。奇偶校驗信息被存放著一個或者多個專門的磁盤上。

2.1.5 SAN

SAN(StorageAreaNetwork,存儲區域網)是一個高速的子網,這個子網中的設備可以從你的主網卸載流量。通常SAN由RAID陣列連接光纖通道(FibreChannel)組成,SAN和服務器和客戶機的數據通信通過SCSI命令而非TCP/IP,數據處理是“塊級”(blocklevel)。

SAN通過特定的互連方式連接的若干臺存儲服務器組成一個單獨的數據網絡,提供企業級的數據存儲服務。SAN是一種特殊的高速網絡,連接網絡服務器和諸如大磁盤陣列或備份磁帶庫的存儲設備,SAN置于LAN之下,而不涉及LAN。利用SAN,不僅可以提供大容量的存儲數據,而且地域上可以分散,并緩解了大量數據傳輸對于局域網的影響。SAN的結構允許任何服務器連接到任何存儲陣列,不管數據置放在哪里,服務器都可直接存取所需的數據。

2.1.6 NAS

NAS是Network Attached Storage(網絡附加存儲)的簡稱。在NAS存儲結構中,存儲系統不再通過I/O總線附屬于某個服務器或客戶機,而直接通過網絡接口與網絡直接相連,由用戶通過網絡訪問。它是連接到一個計算機網絡的文件層的數據存儲,它可以為不同網絡客戶端提供數據存儲服務。NAS的硬件與傳統的專用文件服務器相似。它們的不同點在于軟件端。NAS中的操作系統和其他軟件只提供數據存儲、數據訪問功能,以及對這些功能的管理。與傳統以服務器為中心的存儲系統相比,數據不再通過服務器內存轉發,直接在客戶機和存儲設備間傳送,服務器僅起控制管理的作用。

2.2 IO配置

在借助各種成熟的存儲技術的基礎上,合理配置系統的IO分布及系統IO配置能大量減少系統在生產運行中出現IO性能及相關問題的幾率。當然,這些配置是我們在布置數據庫系統時初始建議,對于復雜的系統來說,很多配置(如一些存儲相關的參數)是需要根據系統的運行狀況進行調優的。

在數據庫系統中,如果某個文件或者某塊磁盤上存在遠遠高于其他文件或磁盤的大量IO訪問,我們就稱這個文件或磁盤為熱點文件/磁盤。我們在做IO規劃時的一個重要目標就是要消除系統中熱點文件/磁盤的存在,使整個系統的IO負載相對平衡。

2.2.1 條帶化的設置

由于現在的存儲技術成熟、成本降低,大多數系統都采用條帶化來實現系統的IO負載分擔。如果操作系統有LVM(Logical Volume Manager邏輯卷管理器)軟件或者硬件條帶設備,我們就可以利用這些攻擊來分布IO負載。當使用LVM或者硬件條帶時,決定因素是條帶深度(stripe depth)和條帶寬度(stripe width):

  • 條帶深度指的是條帶的大小,也叫條帶單元;
  • 條帶寬度指的是條帶深度的產量或者一個條帶集中的驅動數;

需要根據系統的IO要求來合理的選擇這些數據。對于Oracle數據庫系統來數,比較合理的條帶深度是從256K到1M。下面分析影響條帶深度和條帶寬度的影響因素。

2.2.1.1 條帶深度

為了提高IO效率,我們要盡量使一次邏輯IO請求由一塊磁盤的一次物理IO請求。因而影響條帶的一個重要因素就是一次邏輯IO請求的大小。

此外,系統中IO的并發度不同我們對條帶的配置要求也不同。例如,在高并發度且IO請求的大小都比較小的情況下,我們希望一塊磁盤能同時響應多個IO操作;而在那些存在大IO請求的低并發度系統中,我們可能就需要多塊磁盤同時響應一個IO請求。無論是一個磁盤還是多個磁盤響應IO請求,我們的一個原則是讓一次邏輯IO能被一次處理完成。

下面先看下影響IO大小的操作系統和Oracle的相關參數:

  • db_block_size:Oracle中的數據塊大小,也決定了Oracle一次單個IO請求中的數據塊的大小;
  • db_file_multiblock_read_count:在多數據塊讀時,一次讀取數據塊的數量,它和參數db_block_size一起決定了一次多數據塊讀的大小,它們的乘積不能大于操作系統的最大IO大小;
  • 操作系統的數據塊大小:這個參數決定拉Redo Log和Archive Log操作時的數據塊大小,對于大多數Unix系統來說,該值為512K;
  • 最大操作系統IO大小:決定了一次單個的IO操作的IO大小的上限,對于大多數Unix系統來說,由參數max_io_size設置;
  • sort_area_size:內存中sort area的大小,也決定了并發排序操作時的IO大小;
  • hash_area_size:內存中hash area的大小,也決定了哈希操作的IO大小。

其中,前面兩個是最關鍵的兩個參數。

在OLTP系統中,會存在大量小的并發的IO請求。這時就需要考慮選擇比較大的條帶深度。使條帶深度大于IO大小就稱為粗粒度條帶(Coarse Grain Striping)。在高并行度系統中,條帶深度為(n * db_block_size),其中n為大于1的整數。

通過粗粒度條帶能實現最大的IO吞吐量(一次物理IO可以同時響應多個并發的邏輯IO)。大的條帶深度能夠使像全表掃描那樣的多數據塊讀操作由一個磁盤驅動來響應,并提高多數據塊讀操作的性能。

在低并發度的DSS系統中,由于IO請求比較序列化,為了避免出現熱點磁盤,我們需要避免邏輯IO之由一塊磁盤處理。這是,粗粒度條帶就不適合了。我們選擇小的條帶深度,使一個邏輯IO分布到多個磁盤上,從而實現IO的負載均衡。這就叫細粒度條帶。條帶深度的大小為(n * db_block_size),其中n為小于多數據塊讀參數(db_file_multiblock_read_count)大小的整數。

另外,IO過程中,你無法保證Oracle數據塊的邊界能和條帶單元的大小對齊。如果條帶深度大小和Oracle數據塊大小完全相同,而它們的邊界沒有對齊的話,那么就會存在大量一個單獨的IO請求被兩塊磁盤來完成。

在OLTP系統中,為了避免一個邏輯IO請求被多個物理IO操作完成,條帶寬度就需要設置為兩倍或者兩倍以上于Oracle數據塊大小。例如,如果條帶深度是IO大小的N倍,對于大量并發IO請求,我們可以保證最少有(N-1)/ N的請求是由一塊磁盤來完成。

2.2.1.2 條帶寬度

正如我們前面所述,無論是一個還是多個磁盤響應一個邏輯IO,我們都要求IO能被一次處理。因而在確定了條帶深度的基礎上,我們需要保證條帶寬度 >= IO請求的大小 / 條帶深度。

此外,考慮到以后系統容量的擴充,我們也需要規劃好條帶寬度。

如今大多數LVM都支持在線動態增加磁盤。也就是在磁盤容量不足時,我們可以隨時將新磁盤加入到一個已經使用的邏輯卷中。這樣的話,我們在設置邏輯卷時就可以簡單地將所有磁盤都歸入到一個卷中去。

但是,有些LVM可能還不支持動態增加磁盤。這時我們就需要考慮以后的容量擴充對IO均衡的影響了。因為你新增加的磁盤無法加入原有卷,而需要組成一個新的卷。但一般擴充的容量和原有容量比較相對比較小,如果原有卷的條帶寬度比較大的話,新增加的卷的條帶寬度無法達到其大小,這樣就會使新、舊卷之間出現IO失衡。

例如,一個系統的初始配置是一個包含64塊磁盤、每塊磁盤大小為16G的單一邏輯卷。磁盤總的大小是1T。隨著數據庫的數據增長,需要增加80G的空間。我們把新增加的5個16G磁盤再組成一個邏輯卷。這樣就會導致兩個卷上的IO失衡。為了避免這種情況。我們可以將原有磁盤配置成每個條帶寬度為8個磁盤的8個邏輯卷,這樣在新增加磁盤時可以也增加為8個磁盤的新卷。但必須要保證8個磁盤的條帶寬度能夠支持系統的每秒IO吞吐量。

如果你的條帶寬度設置得比較小,就需要估算出你的各個數據庫文件的IO負載,并根據負載量不同將他們分別部署到不同卷上一分擔IO負載。

2.2.2 人工條帶

如果系統不支持LVM或者硬件條帶,IO負載就必須由DBA根據數據庫文件的IO負載不同手工將他們分散到各個磁盤上去以保證整個系統的IO負載均衡。

有許多DBA會將哪些使用頻率非常高的表和它的索引分開存儲。但實際上這種做法并不正確。在一個事務中,索引會先被讀取到然后再讀取表,它們的IO操作是有前后順序的,因此索引和表存儲在同一個磁盤上是沒有沖突的。僅僅因為一個數據文件即包含了索引又包含了數據表而將它分割是不可取的。我們需要根據文件上的IO負載是否已經影響到了數據庫的性能來決定是否將數據文件分割。

為了正確分布文件,我們首先必須先了解各個數據庫文件的IO負載需求以及IO系統的處理能力。鑒定出每個文件的IO吞吐量。找出哪些文件的IO吞吐率最高而哪些IO量很少,將它們分散分布到所有磁盤上去以平衡IO吞吐率。

如果你不了解或者無法預計文件的IO負載,就只能先估計他們的IO負載來規劃文件分布,在系統運行過程中再做調整。

2.2.3 文件分離

無論是采用操作系統條帶化還是手工IO分布方式,如果IO系統或者IO規劃布置無法滿足IO吞吐率的要求,我們就需要考慮將高IO吞吐率的文件和其他文件分離。我們可以在存儲規劃階段或者系統運行階段找出那樣的文件。

除了IO吞吐率,在決定是否分割文件時,我們還需要考慮可恢復性以及數據容量擴張問題。

但是在分割文件之前,一定要確認存在IO瓶頸,然后再根據產生IO瓶頸的數據定位到存在高IO吞吐率的文件(熱點文件)。

2.2.3.1 表、索引和臨時表空間

如果具有高IO吞吐率的數據文件屬于包含表和索引的表空間,我們就需要找出這些文件的IO是否可以通過SQL語句調優或者優化應用程序來降低。

如果具有高IO吞吐率的數據文件屬于臨時表空間,那我們就需要檢查是否可以通過避免或調優SQL語句的排序操作來降低IO。

經過應用調優后,如果IO分布仍然無法滿足IO吞吐的要求,我們就需要考慮分離高IO吞吐率的數據文件了。

2.2.3.2 Redo Log文件

如果具有高IO吞吐率的文件是Redo Log文件,則需要考慮將Redo Log文件與其他文件分離,可以通過以下配置來實現:

  • 將所有Redo Log文件放到沒有任何其他文件的磁盤上去。考慮到可恢復性,需要將一個Redo Log組中的成員文件分別放到不同的物理磁盤上去;
  • 將每個Redo Log組放到一個沒有任何其他文件的單獨磁盤上;
  • 通過操作系統條帶化工具,將Redo Log文件條帶化分布到多個磁盤上去;
  • 不要將Redo Log文件放到RAID 5上去

Redo Log文件是由LGWR進程序列化的寫入的。如果在同一個磁盤上不存在并發的其他IO操作,寫入效率就更高。我們需要確認已經沒有其他優化調整空間再考慮分割Redo Log文件。如果系統支持AIO但還沒有激活該特性,可以考慮激活AIO看是否能解決Redo Log的IO性能瓶頸。

2.2.3.3 歸檔Redo Log

如果歸檔變慢,我們也許可以通過使LGWR的寫操作與Archive進程的讀操作分離來避免LGWR進程魚Archive進程直接的IO沖突。我們可以同交替成組存放Redo Log文件來實現。

例如,我們有四組Redo Log,每組包含兩個Log文件:(A1,A2)、(B1,B2)、(C1,C2)、(D1,D2)。我們就可以以下面這種存放方式將它們分布存儲到四個磁盤上去來實現磁盤分離訪問:(A1,C1)、(A2、C2)、(B1,D1)、(B2,D2)。

當LGWR進程做日志切換時,如從A組切換到B組,LGWR開始向B組寫Redo Log(第三、四塊磁盤),而Archive進程則從B組讀取數據(第一、二塊磁盤)寫入歸檔文件中去,他們分別訪問的是不同磁盤,因而避免了IO沖突。

2.3 三種簡單的配置方法

這里給出三種簡單的操作系統IO配置的例子,包括如何簡單地計算來決定磁盤的拓撲結構、條帶深度等等。

2.3.1 將所有文件條帶化到所有磁盤上去

IO配置最簡單的方法就是建立一個大的邏輯卷,將所有磁盤都條帶化到這個卷中去。考慮到可恢復性,這個卷需要被鏡像(RAID 1)。每個磁盤的條帶深度必須大于頻繁執行的IO操作的最大IO大小。這種配置對大多數情況都能提供足夠的性能支持。

2.3.2 將歸檔日志放到另外的磁盤上去

在歸檔模式下,如果歸檔文件也和其他文件放在同一個條帶化的卷中,那么當歸檔進程對Redo Log進行歸檔時,會大大增加磁盤的IO負載。將歸檔日志轉移到其他磁盤上有如下好處:

  • 歸檔進程效率提高;
  • 當歸檔時,其他進程受到歸檔進程的影響

歸檔日志的磁盤數由歸檔日志產生的頻率以及歸檔存儲容量決定。

2.3.3 將Redo Log文件放到另外的磁盤上去

在更新非常頻繁的OLTP系統中,Redo Log的寫操作非常頻繁。將Redo Log文件轉移到其他磁盤上可以有如下好處:

  • 寫Redo Log的讀寫效率最高,因而事務的執行也能獲得最佳性能;
  • 寫Redo Log操作不會影響任何其他IO操作

Redo Log的磁盤數量有Redo Log的大小決定。由于現在的磁盤容量都非常大,通常配置兩個磁盤(如果做鏡像則需要四塊)就足夠了。并且,根據我們前面的分析,將Redo Log文件交互的存放到兩塊磁盤上去能避免LGWR進程的寫操作與ARCH進程的讀操作之間的IO沖突。

3 Oracle中的IO問題及其解決思路

對于負載偏重點不同,我們可以簡單的將數據庫系統分為CPU負載系統(CPU Bound System)和IO負載系統(IO Bound System)。顧名思義,CPU負載系統的資源瓶頸在于CPU,而IO負載系統的瓶頸在于磁盤IO。

我們可以通過操作系統的一些命令來確認一個系統是否是存在IO負載。在UNIX下,可以使用"iostat"或者"sar -d"來看系統的IO情況;在windows下,可以通過系統的性能監視器查看,但由于性能監控器中看到的IO是靜態的IO總量信息,并不直觀,因此也可以用本站的 TopShow 工具來查看實時的IO信息。

在UNIX系統下,發現CPU IDEL很低并不一定代表這是一個CPU負載系統。一個IO負載系統在表面上看CPU的IDEL值也可能很低:

    
      oracle@db01:/export/home/oracle> sar -u 1 10
    
  
    
  
    
      HP-UX hkhpdv45 B.11.23 U ia64 10/24/07
    
  
    
  
    
      09:43:05 %usr %sys %wio %idle
    
  
    
      09:43:06 43 25 30 1
    
  
    
      09:43:07 44 36 191
    
  
    
      09:43:08 23 27 44 6
    
  
    
      09:43:09 12 37 501
    
  
    
      09:43:10 1036 513
    
  
    
      09:43:11 15 34 42 9
    
  
    
      09:43:12 18 36 443
    
  
    
      09:43:13 17 35 462
    
  
    
      09:43:14 12 32 524
    
  
    
      09:43:15 12 31 561
    
  
    
  
    
      Average 21 33 43 3
    
  

我們可以注意到,實際上WIO是引起CPU IDEL過低的主要原因。WIO是當一個進程需要運行或已經運行后,因為需要等待IO事件而被阻塞了。事實上CPU是處于IDEL狀態(在某些系統中,已經將WIO取消并歸為IDEL),真正的原因是系統中存在IO瓶頸。

通過iostat或者sar -d我們可以找出存在IO瓶頸的磁盤設備,如果該磁盤設備是用于Oracle 數據庫存儲文件的,我們可以判斷出是數據庫存在IO問題。在windows下,可以通過 TopShow 來找出哪個進程正在進行大量IO傳輸,如果是Oracle進程,也可以判斷為是數據庫存在IO問題。

確認系統存在IO問題后,我們就需要定位到底是什么引起的IO問題,該采取什么措施來解決問題。根據我們前面的介紹,Oracle中存在各種IO,要定位IO,最好的工具是statspack(在10g以后,可以用AWR)。通過statspack report的Top 5 Events,我們可以看到對系統系能影響最大的5個等待event,而不同的IO問題會對應不同Event,所以,我們可以根據這些event采取不同的措施來解決IO問題。下面是一個典型的IO負載系統的Top 5 Event:

    
      Top 5 Timed Events
    
  
    
      ~~~~~~~~~~~~~~~~~~ % Total
    
  
    
      Event Waits Time (s) Ela Time
    
  
    
      -------------------------------------------- ------------ ----------- --------
    
  
    
      db file sequential read 70,575,969 344,200 53.34
    
  
    
      db file scattered read 11,240,748 163,242 25.30
    
  
    
      log file sync 657,241 36,363 5.64
    
  
    
      CPU time 35,290 5.47
    
  
    
      log file parallel write 833,799 20,767 3.22
    
  

可以看到,前兩個時間“db file sequential read”和“db file scattered read”分別占了總等待時間的53.34%和25.30%,而我們前面提到這兩個事件分別是由索引掃面和全表掃面(或快速索引掃面)引起的,因此,能解決索引掃面問題和全表掃面問題就能解決這個系統的IO瓶頸。

IO問題到底對CPU有多大影響呢?我們用以上例子中的數據分析一下。從等待時間統計數據中,我們看到的是時間在總等待時間中所占的比例。而系統的“總響應時間 ”= “等待時間 ”+ “CPU工作時間”(注意,上面Top 5事件中的“CPU Time”不是指CPU的工作時間,而是指CPU的等待時間)。“CPU工作時間”的數據我們可以在“Instance Activities Stats for DB”這一分類統計數據中找到:

    
      Statistic Total per Second per Trans
    
  
    
      --------------------------------- ------------------ -------------- ------------
    
  
    
      CPU used by this session 17,136,868 396.7 15.5
    
  

先計算出“總等待時間” = 344,200 * 100% / 53.34% = 645,294s

“總響應時間” = “總等待時間” + “CPU工作時間” = 645,294 + 17,136,868 = 17,782,162s

我們可以算出“CPU工作時間”、“db file sequential read”和“db file scattered read”分別在“總響應時間中所占的比例為:

CPU工作時間 = 17,136,868 / 17,782,162 = 96.4%

“db file sequential read” = 344,200 / 17,782,162 = 1.9%

“db file scattered read” = 163,242 / 17,782,162 = 0.9%

可見,IO事件所引起的等待時間在總響應時間所占比例并不大。因此,我們在做系統優化之前先分析系統是CPU負載系統還是IO負載系統對于我們的優化方向和最終的優化效果起很大的作用。

以下事件是可能由IO問題引起的等待事件,在IO負載系統中,我們要特別關注這些事件:

  • 與數據文件相關的IO事件

'db file sequential read'

'db file scattered read'

'db file parallel read'

'direct path read'

'direct path write'

'direct path read (lob)'

'direct path write (lob)'

  • 與控制文件相關的IO事件

'control file parallel write'

'control file sequential read'

'control file single write'

  • 與Redo日志相關的IO事件

'log file parallel write'

'log file sync'

'log file sequential read'

'log file single write'

'switch logfile command'

'log file switch completion'

'log file switch (clearing log file)'

'log file switch (checkpoint incomplete)'

'log switch/archive'

'log file switch (archiving needed)'

  • 與Buffer Cache相關的IO事件

'db file parallel write'

'db file single write'

'write complete waits'

'free buffer waits'

下面我們就分別介紹如何解決IO問題。

3.1 IO調優的思路及常用手段

通過對statspack或者awr報告的分析,我們可以得知是那些IO相關事件引起的IO問題。針對不同的事件,可以采取不同的分析、處理方法。而有一些通用的方法并不是針對特定的事件的。我們這里先介紹一下這些方法。

3.1.1 通過SQL調優來減少IO請求

一個沒有任何用戶SQL的數據庫幾乎不產生任何IO。基本上數據庫所有的IO都是直接或間接由用戶提交的SQL所導致的。這意味著我們可以通過控制單個SQL產生的IO來降低數據庫總的IO請求。而通過SQL調優來降低SQL查詢計劃中的IO操作次數則是降低SQL產生IO的最好方法。數據庫的性能問題通常是由少數幾個SQL語句所導致的,它們產生了大量IO導致了整個數據庫的性能下降。優化幾條問題語句往往就能解決整個數據庫的IO性能問題。

從Oracle 10g開始,ADDM能夠自動檢測出問題語句,同時,再通過查詢優化建議器能夠自動優化語句并降低它們對IO的消耗。關于ADDM和查詢優化建議器可以參考文章 《Oracle 10G 新特性——ADDM和查詢優化建議器》

3.1.2 通過調整實例參數來減少IO請求

在這種方法中,主要有兩種途徑來實現對IO的優化。

  • 使用內存緩存來減少IO

通過一些內存緩存,如Buffer Cache、Log Buffer、Sort Area,可以降低數據庫對IO的請求。

當Buffer Cache被增大到一定大小時,絕大多數結果可以直接從緩存中獲取到,而無需從磁盤上讀取了。而在進行排序操作時,如果Sort Area足夠大,排序過程中產生的臨時數據可以直接放在內存中,而無需占用臨時表空間了。

  • 調整multiblock IO(多數據塊IO)的大小

控制Multiblock IO的參數叫DB_FILE_MULTIBLOCK_READ_COUNT,它控制在多數據塊讀時一次讀入數據塊的次數。適當增加這個參數大小,能夠提高多數據塊操作(如全表掃描)的IO效率。例如,讀取100M數據,如果每次讀取1M一共讀取100次的效率就比每次讀取100K一共讀取1000次更快。但是這個數字達到一定大小后,再增加就作用不大了:每次10M一共讀100次來讀取1G的數據的效率和單獨一次讀取1G數據的效率是沒有多大區別的。這是因為IO效率受到2個因素的影響:IO建立時間和IO傳輸時間。

IO建立時間對于不同IO大小來說都是相同的,它決定了對小IO的總的IO時間,增大Multiblock IO大小可以減少IO建立時間;

IO傳輸時間與IO大小是成正比的,在小IO時,IO傳輸時間一般比IO建立時間少,但對于大IO操作來說,IO傳輸時間決定了總的IO時間。因此Multiblock IO大小增大到一定大小時,它對總的IO時間影響就不大了。

3.1.3 在操作系統層面優化IO

如我們前面所介紹的,利用一些操作系統提供的提升IO性能的特性,如文件系統的異步IO、Direct IO等來優化數據庫系統的IO性能。另外一種方法就是增加每次傳輸的最大IO大小的限制(大多數Unix系統中,由參數max_io_size控制)。

3.1.4 通過Oracle ASM實現對IO的負載均衡

ASM(Automatic Storage Manager自動存儲管理)是從Oracle 10g開始引入的。它是一個建立在數據庫內核中的文件系統和卷管理器。它能自動將IO負載均衡到所有可用的磁盤啟動器上去,一避免“熱區”。ASM能防止碎片,因此無需重建數據來回收空間。數據被均衡分布到所有硬盤上。

3.1.5 通過條帶化、RAID、SAN或者NAS實現對IO的負載均衡

這個方法通過一些成熟的存儲技術,如條帶化、RAID、SAN和NAS,來將數據庫IO分布到多個可用的物理磁盤實現負載均衡,以避免在還存在空閑可用磁盤時出現的磁盤爭用和IO瓶頸問題。

關于這幾種存儲技術,我們文章的前面部分都有做介紹。

3.1.6 通過手工布置數據庫文件到不同的文件系統、控制器和物理設備上來重新分布數據庫IO

當數據庫系統中缺乏以上各種存儲技術手段時,我們可以考慮使用這種方式。這樣做的目的是使數據庫的IO得到均勻分布,從而避免在還有空閑磁盤時出現磁盤爭用和IO瓶頸問題。當然這種手工分布IO方法是無法達到以上的自動分布IO的效果的。

3.1.7 其他手段

系統中總會存在一些IO是無法消除或降低的。如果采用以上手段還不能滿足IO性能要求的話,可以考慮這兩種方法:

  • 將老數據移除你的生產數據庫(Housekeep)
  • 采用更多、更快的硬件

3.2 數據文件相關的IO事件

數據庫系統中的大多數的IO請求都是針對數據文件的。因此大多數情況下,與數據文件相關的IO事件是引起系統IO性能的主要原因。這些事件也是我們文章需要重點介紹的事件。下面分別針對不同事件介紹問題的解決思路。

3.2.1 db file sequential read

這個事件是是最常見的IO等待事件。它一般發生在讀取單獨數據塊時,如讀取索引數據塊或者通過索引訪問一個表數據塊,另外在讀取數據文件頭數據塊時也會發生db file sequential read等待事件。

當發現這個等待事件成為系統等待事件中的主要事件,我們可以通過一下方法來處理:

3.2.1.1 優化Top SQL

從statspack或者awr報告中的“SQL ordered by Reads”部分或者通過V$SQL視圖找出系統中的Top SQL,對SQL進行調優以減少IO請求。

  • 當SQL中存在Index Range Scan時,如果訪問的索引的選擇性不好就會導致需要訪問過多的數據塊,這時可以通過建立一個、或強制SQL使用一個已經存在的選擇性更好的索引。這樣使我們訪問更少的數據塊來獲取到需要的數據。

    
      SQL> select object_id, object_name
    
  
    
       2 from t_test1
    
  
    
       3 where owner = 'SYS'
    
  
    
       4 and created > sysdate - 30;
    
  
    
  
    
      no rows selected
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 4014220762
    
  
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)
    
  
    
      | Time |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT | | 1 | 39 | 11 (0)
    
  
    
      | 00:00:01 |
    
  
    
      |* 1 | TABLE ACCESS BY INDEX ROWID| T_TEST1 | 1 | 39 | 11 (0)
    
  
    
      | 00:00:01 |
    
  
    
      |* 2 | INDEX RANGE SCAN | T_TEST1_IDX1 | 576 | | 1 (0)
    
  
    
      | 00:00:01 |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       1 - filter("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
    
  
    
  
    
  
    
      Statistics
    
  
    
      ----------------------------------------------------------
    
  
    
       0 recursive calls
    
  
    
       0 db block gets
    
  
    
       658 consistent gets
    
  
    
       45 physical reads
    
  
    
       0 redo size
    
  
    
       339 bytes sent via SQL*Net to client
    
  
    
       374 bytes received via SQL*Net from client
    
  
    
       1 SQL*Net roundtrips to/from client
    
  
    
       0 sorts (memory)
    
  
    
       0 sorts (disk)
    
  
    
       0 rows processed
    
  
    
  
    
      SQL> create index t_test1_idx2 on t_test1(owner, created);
    
  
    
  
    
      Index created.
    
  
    
  
    
      SQL> select object_id, object_name
    
  
    
       2 from t_test1
    
  
    
       3 where owner = 'SYS'
    
  
    
       4 and created > sysdate - 30;
    
  
    
  
    
      no rows selected
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 3417015015
    
  
    
      ---------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)
    
  
    
      | Time |
    
  
    
      ---------------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT || 49 | 1911 | 2 (0)
    
  
    
      | 00:00:01 |
    
  
    
      | 1 | TABLE ACCESS BY INDEX ROWID| T_TEST1 | 49 | 1911 | 2 (0)
    
  
    
      | 00:00:01 |
    
  
    
      |* 2 | INDEX RANGE SCAN | T_TEST1_IDX2 | 49 | | 1 (0)
    
  
    
      | 00:00:01 |
    
  
    
      ---------------------------------------------------------------------------------
    
  
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       2 - access("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
    
  
    
  
    
  
    
      Statistics
    
  
    
      ----------------------------------------------------------
    
  
    
       1 recursive calls
    
  
    
       0 db block gets
    
  
    
       2 consistent gets
    
  
    
       1 physical reads
    
  
    
       0 redo size
    
  
    
       339 bytes sent via SQL*Net to client
    
  
    
       374 bytes received via SQL*Net from client
    
  
    
       1 SQL*Net roundtrips to/from client
    
  
    
       0 sorts (memory)
    
  
    
       0 sorts (disk)
    
  
    
       0 rows processed
    
  

  • 如果索引存在碎片,那每個索引數據塊上的索引數據就更少,會導致我們需要訪問更多的索引數據塊。這時,我們需要考慮重建索引來釋放碎片;

判斷一個所以是否需要重建,我們介紹一個簡單的方法:對一個索引進行結構分析后,如果該索引占用超過了一個數據塊,且滿足以下條件之一:B-tree樹的高度大于3;使用百分比低于75%;數據刪除率大于15%,就需要考慮對索引重建:

    
      SQL> analyze index t_test1_idx1 compute statistics;
    
  
    
  
    
      Index analyzed.
    
  
    
  
    
      SQL> analyze index t_test1_idx1 validate structure;
    
  
    
  
    
      Index analyzed.
    
  
    
  
    
      SQL> select btree_space, -- if > 8192(塊的大小)
    
  
    
       2 height, -- if > 3
    
  
    
       3 pct_used, -- if < 75
    
  
    
       4 del_lf_rows/(decode(lf_rows,0,1,lf_rows)) *100 as deleted_pct -- if > 20%
    
  
    
       5 from index_stats;
    
  
    
  
    
      BTREE_SPACE HEIGHT PCT_USED DELETED_PCT
    
  
    
      ----------- ---------- ---------- -----------
    
  
    
       880032 2 89 0
    
  

如果使用的索引的聚簇因子(Clustering Factor)很大,說明一條索引記錄指向多個數據塊,在返回結果時需要讀取更多的數據塊。通過重建表可以降低聚簇因子,因而可以在查找索引時減少表數據塊的訪問塊數。

聚簇因子說明了表數據的物理存儲位置相對于一個索引的排序性的符合程度。例如,一個非唯一索引是建立在A字段上的,如果表數據的存儲是以A字段的順序存儲的,則索引與數據的關系如下圖:

此時,索引的聚簇因子很低,從圖上看到,假如我們需要獲取A=A2的數據,只需要讀取一個數據塊就可以了;

相反,如果表數據物理存儲順序和索引順序相差很大,就會出現下面的情況:

這時該索引的聚簇因子就很大,可以看到,如果需要獲取A=A2的數據,我們需要讀取4塊或更多的數據塊。

對索引進行分析后,我們可以從視圖DBA_INDEXES中獲取到索引的聚簇因子,字段名為Clustoring_Factor。如果一個索引是一張表主要被使用的索引(或者是該表的唯一索引),且它的聚簇因子過高導致IO請求過高的話,我們可以考慮采取以下措施來降低IO:

1) 以索引字段的順序重建表以降低聚簇因子,可以用以下語句重建表(當然,你還需要重建觸發器、索引等對象,還可能需要重建、重新編譯有關聯對象):

    
      CREATE new_table AS SELECT * FROM old_table ORDER BY A;
    
  

2) 建立基于索引字段IOT(索引表)。

如果該索引不是表的主要索引,只是被少量語句引用到,按照以上方式處理的話反而可能會使其他使用更加頻繁的索引的聚簇因子增大,導致系統性能更差。這時我們可以建立包含返回字段的索引,以避免“TABLE ACCESS BY INDEX ROWID”。如以下例子:

    
      SQL> set autot trace
    
  
    
      SQL> select status from t_test1
    
  
    
       2 where owner = 'DEMO';
    
  
    
  
    
      576 rows selected.
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 4014220762
    
  
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)
    
  
    
      | Time |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT | | 576 | 6336 | 11 (0)
    
  
    
      | 00:00:01 |
    
  
    
      | 1 | TABLE ACCESS BY INDEX ROWID| T_TEST1 | 576 | 6336 | 11 (0)
    
  
    
      | 00:00:01 |
    
  
    
      |* 2 | INDEX RANGE SCAN | T_TEST1_IDX1 | 576 | | 1 (0)
    
  
    
      | 00:00:01 |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       2 - access("OWNER"='DEMO')
    
  
    
  
    
  
    
      Statistics
    
  
    
      ----------------------------------------------------------
    
  
    
       465 recursive calls
    
  
    
       0 db block gets
    
  
    
       222 consistent gets
    
  
    
       43 physical reads
    
  
    
       0 redo size
    
  
    
       8368 bytes sent via SQL*Net to client
    
  
    
       803 bytes received via SQL*Net from client
    
  
    
       40 SQL*Net roundtrips to/from client
    
  
    
       8 sorts (memory)
    
  
    
      0 sorts (disk)
    
  
    
       576 rows processed
    
  
    
  
    
      SQL> create index t_test1_idx3 on t_test1(owner, status) compute statistics;
    
  
    
  
    
      Index created.
    
  
    
  
    
      SQL> select status from t_test1
    
  
    
       2 where owner = 'DEMO';
    
  
    
  
    
      576 rows selected.
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 2736516725
    
  
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time|
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT | | 576 | 6336 | 2 (0)| 00:00:01|
    
  
    
      |* 1 | INDEX RANGE SCAN| T_TEST1_IDX3 | 576 | 6336 | 2 (0)| 00:00:01|
    
  
    
      --------------------------------------------------------------------------------
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       1 - access("OWNER"='DEMO')
    
  
    
  
    
  
    
      Statistics
    
  
    
      ----------------------------------------------------------
    
  
    
      1 recursive calls
    
  
    
       0 db block gets
    
  
    
       43 consistent gets
    
  
    
       3 physical reads
    
  
    
       0 redo size
    
  
    
       8152 bytes sent via SQL*Net to client
    
  
    
       803 bytes received via SQL*Net from client
    
  
    
       40 SQL*Net roundtrips to/from client
    
  
    
       0 sorts (memory)
    
  
    
       0 sorts (disk)
    
  
    
       576 rows processed
    
  

  • 通過分區裁剪(partition pruning)技術來減少的SQL對數據塊的訪問。

采用分區裁剪技術,Oracle優化器會先分析FROM和WHERE字句,在建立訪問分區列表時將那些不會被訪問到的分區排除。例如,我們的表T_TEST1的owner字段的值有“SYS、SYSTEM、XDB、DEMO、TEST”,如果我們按照owner字段建立的是分區表:

    
      CREATE TABLE t_test1
    
  
    
      (object_id NUMBER(5), 
    
  
    
      object_name VARCHAR2(30),
    
  
    
       ownerVARCHAR2(20),
    
  
    
       created DATE)
    
  
    
      PARTITION BY LIST(owner)
    
  
    
      (
    
  
    
      PARTITION owner_sys VALUES('SYS', 'SYSTEM'),
    
  
    
      PARTITION owner_xdb VALUES ('XDB'),
    
  
    
      PARTITION owner_demo VALUES('DEMO'),
    
  
    
      PARTITION owner_test VALUES('TEST'),
    
  
    
      PARTITION owner_others VALUES(DEFAULT)
    
  
    
      );
    
  

則對于以下語句:

    
      select object_name
    
  
    
      from t_test1
    
  
    
      where owner in ('DEMO', 'TEST')
    
  
    
      and created > sysdate - 30;
    
  

優化器會先將分區owner_sys、owner_xdb、owner_others從分區訪問列表中裁剪出去,只訪問分區owner_demo和owner_test上的數據或者通過這兩個分區上的索引來訪問數據。

3.2.1.2 處理非SQL導致的IO問題

如果從statspack或者AWR報告中找不到明顯產生db file sequential read事件的SQL,則該等待事件可能是由于以下原因導致的:

  • 熱點數據文件或磁盤

數據文件所在的磁盤IO負荷過重導致對IO請求反映慢,這時,我們可以通過statspack或AWR報告中的“File I/O Statistics”部分(或者通過V$FILESTAT視圖)來找到熱點磁盤:

Statspack report:

    
      Tablespace Filename
    
  
    
      ------------------------ ----------------------------------------------------
    
  
    
       Av Av Av Av Buffer Av Buf
    
  
    
       Reads Reads/s Rd(ms) Blks/Rd Writes Writes/s Waits Wt(ms)
    
  
    
      -------------- ------- ------ ------- ------------ -------- ---------- ------
    
  
    
      AFW_DATA /export/home/icssprd/data/data17/icssprd_afw_data_01
    
  
    
       726 0 4.3 1.0 381 0 0
    
  
    
  
    
      AFW_INDX /export/home/icssprd/data/data18/icssprd_afw_indx_01
    
  
    
       1,741 0 6.3 1.0 2,104 0 0
    
  
    
  
    
      CSS_AN_DATA /export/home/icssprd/data/data03/icssprd_css_an_data
    
  
    
       200,649 5 1.8 3.2 24,192 1 0
    
  
    
       /export/home/icssprd/data/data04/icssprd_css_an_data
    
  
    
       242,462 6 1.6 3.1 26,985 1 3 6.7
    
  
    
  
    
      CSS_AN_INDX /export/home/icssprd/data/data13/icssprd_css_an_indx
    
  
    
       70,789 2 5.0 1.6 5,330 0 0
    
  
    
  
    
      CSS_AUDIT_RESOURCES_DATA /export/home/icssprd/data/data10/icssprd_css_audit_r
    
  
    
       2,394 0 0.6 1.0 1,781 00
    
  
    
  
    
      CSS_AUDIT_RESOURCES_INDX /export/home/icssprd/data/data11/icssprd_css_audit_r
    
  
    
       248 0 4.3 1.0 52 0 0
    
  
    
  
    
      ... ...
    
  

視圖:

    
      SQL> select b.name, phyrds, phywrts
    
  
    
       2 from V$FILESTAT a, V$DATAFILE b
    
  
    
       3 where a.file# = b.file#;
    
  
    
  
    
      NAME
    
  
    
      --------------------------------------------------------------------------------
    
  
    
       PHYRDS PHYWRTS
    
  
    
      ---------- ----------
    
  
    
      C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_SYSTEM_20TFOB4Q_.DBF
    
  
    
       132767 11565
    
  
    
  
    
      C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_UNDOTBS1_20TFQP78_.DBF
    
  
    
       1943 19924
    
  
    
  
    
      C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_SYSAUX_20TFSGC6_.DBF
    
  
    
      659458 100811
    
  
    
  
    
      ... ...
    
  

找到熱點數據文件(磁盤)后,我們可以考慮將數據文件轉移到性能更高的存儲設備上去,或者利用我們上述說的條帶化、RAID等存儲技術來均衡IO負荷。

  • 熱點數據段

從Oracle9.2開始,出現了數據段的概念。每個表和索引都存儲在自己的數據段中。我們可以通過視圖V$SEGMENT_STATISTICS查找物理讀最多的段來找到熱點數據段。通過對熱點段的分析,考慮采用重建索引、分區表等方式來降低該數據段上的IO負荷。

    
      SQL> select owner, object_name, tablespace_name, object_type, value
    
  
    
       2 from V$SEGMENT_STATISTICS
    
  
    
       3 where statistic_name = 'physical reads'
    
  
    
       4 order by value desc;
    
  
    
  
    
      OWNER OBJECT_NAME
    
  
    
      ------------------------------ ------------------------------
    
  
    
      TABLESPACE_NAMEOBJECT_TYPE VALUE
    
  
    
      ------------------------------ ------------------ ----------
    
  
    
      SYS CONTEXT$
    
  
    
      SYSTEM TABLE 71
    
  
    
  
    
      SYS I_CONTEXT
    
  
    
      SYSTEMINDEX 70
    
  
    
  
    
      ... ...
    
  

另外,我們還可以根據視圖v$session_wait中的P1(熱點段所在的數據文件號)、P2(發生db file sequential read事件的起始數據塊)、P3(數據塊的數量,db file sequential read讀取數據塊數量為1)來定位出熱點段:

先找出文件號、起始數據塊、數據塊數量:

    
      SQL> select p1 "fileid", p2 "block_id", p3 "block_num"
    
  
    
       2 from v$session_wait
    
  
    
       3 where event = 'db file sequential read';
    
  
    
  
    
       fileid block_id block_num
    
  
    
      ---------- ---------- ----------
    
  
    
       396 44869 1
    
  

然后根據找出的文件號、起始數據塊、數據塊數量來定位出數據段:

    
      SQL> select
    
  
    
       2 segment_name "Segment Name",
    
  
    
       3 segment_type "Segment Type",
    
  
    
       4 block_id "First Block of Segment",
    
  
    
       5 block_id+blocks "Last Block of Segment"
    
  
    
       6 from dba_extents
    
  
    
       7 where &fileid = file_id
    
  
    
       8 and &block_id >= block_id
    
  
    
       9 and &block_id <= block_id+blocks;
    
  
    
      Enter value for fileid: 396
    
  
    
      old 7: where &fileid = file_id
    
  
    
      new 7: where 396 = file_id
    
  
    
      Enter value for block_id: 44869
    
  
    
      old 8: and &block_id >= block_id
    
  
    
      new 8: and 44869 >= block_id
    
  
    
      Enter value for block_id: 44869
    
  
    
      old 9: and &block_id <= block_id+blocks
    
  
    
      new 9: and 44869 <= block_id+blocks
    
  
    
  
    
      Segment Name
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      Segment Type First Block of Segment Last Block of Segment
    
  
    
      ------------------ ---------------------- ---------------------
    
  
    
      CSS_TP_SHMT_QUEUE_ACTIVITY
    
  
    
      TABLE 44841 44873
    
  

3.2.1.3 調整Buffer Cache

如果系統中即不存在性能有問題的SQL語句,而且所有磁盤的IO負載也比較均衡(不存在熱地磁盤),則我們需要考慮增加Buffer Cache來降低磁盤IO請求。

在8i,主要是根據緩存命中率(Buffer Cache Hit Ratio)來調整buffer cache。當Buffer Cache調整到一定大小,對命中率沒什么影響了時,就沒有必要在增大Buffer Cache了。可以通過以下語句來查看Buffer Cache命中率:

    
      SQL> select 1-(physical_reads)/(consistent_gets+db_block_gets)
    
  
    
       2 from v$buffer_pool_statistics;
    
  
    
  
    
      1-(PHYSICAL_READS)/(CONSISTENT_GETS+DB_BLOCK_GETS)
    
  
    
      --------------------------------------------------
    
  
    
       .95628981
    
  

在9i中,可以利用statspack report中的Buffer Cache建議部分來調整Buffer Cache的大小。

    
      Buffer Pool Advisory for DB: ICSSPRD Instance: icssprd End Snap: 259
    
  
    
      -> Only rows with estimated physical reads >0 are displayed
    
  
    
      -> ordered by Block Size, Buffers For Estimate
    
  
    
  
    
       Size for Size Buffers for Est Physical Estimated
    
  
    
      P Estimate (M) Factr Estimate Read Factor Physical Reads
    
  
    
      --- ------------ ----- ---------------- ------------- ------------------
    
  
    
      D 304 .1 37,715 9.18 5,928,235,496
    
  
    
      D 608 .2 75,430 6.88 4,443,709,043
    
  
    
      D 912 .3 113,145 5.73 3,699,496,220
    
  
    
      D 1,216 .4 150,860 3.87 2,502,670,372
    
  
    
      D 1,520 .5 188,575 2.32 1,499,049,228
    
  
    
      D 1,824 .6 226,290 1.70 1,099,326,418
    
  
    
      D 2,128 .7 264,005 1.41 912,042,579
    
  
    
      D 2,432 .8 301,720 1.22 790,925,174
    
  
    
      D 2,736 .9 339,435 1.09 703,357,378
    
  
    
      D 2,992 1.0 371,195 1.00 645,905,997
    
  
    
      D 3,040 1.0 377,150 0.99 636,992,420
    
  
    
      D 3,344 1.1 414,865 0.90 583,996,250
    
  
    
      D 3,648 1.2 452,580 0.84 542,063,246
    
  
    
      D 3,952 1.3 490,295 0.79 508,261,496
    
  
    
      D 4,256 1.4 528,010 0.74 480,472,150
    
  
    
      D 4,560 1.5 565,725 0.71 455,533,563
    
  
    
      D 4,864 1.6 603,440 0.67 434,743,759
    
  
    
      D 5,168 1.7 641,155 0.64 416,285,837
    
  
    
      D 5,472 1.8 678,870 0.62 400,208,242
    
  
    
      D 5,776 1.9 716,585 0.60 385,785,401
    
  
    
      D 6,080 2.0 754,300 0.57 365,597,932
    
  
    
       -------------------------------------------------------------
    
  

這里,Est Physical Read Factor是估算的從磁盤物理讀取次數與從buffer cache中讀取的次數的比值。從意見估算的圖表中,當Buffer Cache的增長對該因子影響不大時,則說明無需在增大Buffer Cache,我們就可以去相應臨界點的大小作為Buffer Cache的大小。上述例子中,我們可以考慮設置Buffer Cache大小為2992M。

在Oracle10g中,引入了新的內存管理特性——自動共享內存管理(Automatic Shared Memory Management ASMM)。基于這一特性,oracle能夠自動根據當前的負荷計算出最優的Buffer Cache大小。關于ASMM,可以參見文章《 Oracle內存全面分析 》的SGA_TARGET部分。

我們可以采用多尺寸緩沖池技術將熱點數據段(表或索引)KEEP在緩沖池中:

    
      SQL> alter table t_test1 storage(buffer_pool keep);
    
  
    
  
    
      Table altered.
    
  

關于多尺寸緩沖的更多內容,可以參考文章《 Oracle內存全面分析 》的“多緩沖池部分”部分。

3.2.1.4 Housekeep歷史數據

對于一些被頻繁訪問到的大表,我們需要定期對其做housekeep,將一些不用的、老的數據從表中移除,以減少訪問的數據塊。定期對含有時間軸的Transaction表做housekeep是降低IO負載的重要措施。

3.2.2 db file scattered read

這是另外一個常見的引起數據庫IO性能問題的等待事件。它通常發生在Oracle將“多數據塊”讀取到Buffer Cache中的非連續(分散的 Scattered)區域。多數據塊讀就是我們上述所說的一次讀取“DB_FILE_MULTIBLOCK_READ_COUNT”塊數據塊,前面提到,它通常發生在全表掃描(Full Table Scan)和快速全索引掃描(Fast Full Index Scan)時。當發現db file scattered read等待事件是系統引起IO性能的主要原因時,我們可以采取以下措施對系統進行優化。

3.2.2.1 優化存在Full Table Scan和Fast Full Index Scan的SQL語句

我們可以首先從statspack或者awr報告中的“SQL ordered by Reads”部分中找出存在Full Table Scan和Fast Full Index Scan的Top SQL。因為這些Top SQL往往是整個系統的瓶頸。

從9i開始,我們還可以通過視圖V$SQL_PLAN來查找系統中存在Full Table Scan和Fast Full Index Scan的SQL語句。查找Full Table Scan的語句:

    
      select sql_text from v$sqlarea t, v$sql_plan p
    
  
    
       where t.hash_value=p.hash_value and p.operation='TABLE ACCESS'
    
  
    
       and p.options='FULL';
    
  

查找Fast Full Index Scan的語句

    
      select sql_text from v$sqlarea t, v$sql_plan p
    
  
    
       where t.hash_value=p.hash_value and p.operation='INDEX'
    
  
    
       and p.options='FULL SCAN';
    
  

Full Table Scan通常是由于以下幾個原因引起的:

  • 條件字段上沒有索引;

在這種情況下,如果表的數據量比較大,我們就需要在相應字段上建立起索引。

  • CBO中,對象的統計數據不正確

CBO中,如果對象的統計數據或者其柱狀圖(Histogram)信息不正確,會導致優化器計算出錯誤的查詢計劃,從而選擇全表掃描。這種情況下,我們要做的就重新分析(Analyze)表、索引及字段。

  • CBO中,SQL語句中引用到了無法估算統計數據的對象

在PLSQL中,可以建立一些高級的數據類型,如“TABLE OF”、ARRAY等,通過TABLE、CAST函數可以在SQL語句中將這些對象當成表來處理。而這些對象的數據只存在于調用PLSQL的會話中,因此他們沒有相應的統計數據,Oracle會為他們生產一些假的統計數據以完成查詢計劃代價估算。但是基于這些假的數據計算出的查詢計劃一般是錯誤的。我們可以考慮通過提示來強制SQL使用索引或者強制SQL采用RBO優化器。

此外,如果SQL中引用到了臨時表(Temporary Table)也會產生同樣的問題。其原因和解決方法和上面相同。

  • 優化器認為索引掃描代價過高;

在Oracle中存在一個參數optimizer_index_cost_adj,該參數的值代表一個百分數,如果對索引掃描的代價達到或超過全表掃描的代價的這個百分比值時,優化器就采用全表掃描。

optimizer_index_cost_adj是一個全局性的參數,它的合理值是通過長期調整出來的。一般來說是一個介于1到100之間的數字。我們可以按照以下方法來選取optimizer_index_cost_adj的合理值。

先由以下語句得出optimizer_index_cost_adj的一個初始值:

    
      SQL> select
    
  
    
       2 a.average_wait"Average Waits FTS"
    
  
    
       3 ,b.average_wait "Average Waits Index Read"
    
  
    
       4 ,a.total_waits /(a.total_waits + b.total_waits) "Percent of FTS"
    
  
    
       5 ,b.total_waits /(a.total_waits + b.total_waits) "Percent of Index Scans"
    
  
    
       6 ,(b.average_wait / a.average_wait)*100 "optimizer_index_cost_adj"
    
  
    
       7 from
    
  
    
       8 v$system_event a,
    
  
    
       9 v$system_event b
    
  
    
       10 where a.EVENT = 'db file sequential read'
    
  
    
       11 and b.EVENT = 'db file scattered read';
    
  
    
  
    
      Average Waits FTS Average Waits Index Read Percent of FTS Percent of Index Scans
    
  
    
      ----------------- ------------------------ -------------- ----------------------
    
  
    
      optimizer_index_cost_adj
    
  
    
      ------------------------
    
  
    
       1.25 1.06 .041867874 .958132126
    
  
    
      84.8
    
  

這里,84.8是我們系統的初始值。在系統經過一段時間運行后,再次運行上面的語句,重新調整optimizer_index_cost_adj的值。經過多次如此反復的調整之后,最終上面語句得出值趨于穩定,這時這個值就是符合我們系統性能需求的最合理的值。

當然這個數值也可以通過statspack的歷史數據來調整,在9i中:

    
      select to_char(c.end_interval_time, 'MM/DD/YYYY') "Date",



 sum(a.time_waited_micro)/sum(a.total_waits)/10000 "Average Waits FTS",



 sum(b.time_waited_micro)/sum(b.total_waits)/10000 "Average Waits Index Read",



 (sum(a.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of FTS",



 (sum(b.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of Index Scans",



 (sum(b.time_waited_micro)/sum(b.total_waits)) /



 (sum(a.time_waited_micro)/sum(a.total_waits)) * 100 "optimizer_index_cost_adj"



from dba_hist_system_event a, dba_hist_system_event b, dba_hist_snapshot c



where a.event_name = 'db file scattered read'



and b.event_name = 'db file sequential read'



and a.snap_id = c.snap_id



and b.snap_id = c.snap_id



group by c.end_interval_time



order by 1;
    
  

10g中:

    
      select to_char(c.snap_time, 'MM/DD/YYYY') "Date",



 sum(a.time_waited_micro)/sum(a.total_waits)/10000 "Average Waits FTS",



 sum(b.time_waited_micro)/sum(b.total_waits)/10000 "Average Waits Index Read",



 (sum(a.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of FTS",



 (sum(b.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of Index Scans", (sum(b.time_waited_micro)/sum(b.total_waits)) /
 (sum(a.time_waited_micro)/sum(a.total_waits)) * 100 "optimizer_index_cost_adj"

from stats$system_event a, stats$system_event b, stats$snapshot c

where a.event = 'db file scattered read'

and b.event = 'db file sequential read'

and a.snap_id = c.snap_id

and b.snap_id = c.snap_id

group by c.snap_time

order by 1;
    
  

當optimizer_index_cost_adj的值對于整個系統來說已經是比較合理的值,而某些語句由于該值選擇了全表掃描掃描導致了IO性能問題時,我們可以考慮通過提示來強制語句命中索引。

  • 建立在條件字段上的索引的選擇性不高,結合上一條導致全表掃描;

當索引的選擇性不高,且其代價過高,系統則會選擇全表掃描來讀取數據。這時我們可以考慮通過選擇/建立選擇性比較高的索引,使查詢命中索引從而避免全表掃描。

    
      SQL> create index t_test1_idx1 on t_test1(owner) compute statistics;
    
  
    
  
    
      Index created.
    
  
    
  
    
      SQL> set autot trace
    
  
    
      SQL> select object_name
    
  
    
       2 from t_test1
    
  
    
       3 where owner = 'SYS'
    
  
    
       4 and created > sysdate - 30;
    
  
    
  
    
      no rows selected
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 1883417357
    
  
    
  
    
      -----------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    
  
    
      -----------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT | | 49 | 1715 | 152 (2)| 00:00:02 |
    
  
    
      |* 1 | TABLE ACCESS FULL| T_TEST1 | 49 | 1715 | 152 (2)| 00:00:02 |
    
  
    
      -----------------------------------------------------------------------------
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       1 - filter("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
    
  
    
  
    
      ... ...
    
  
    
  
    
      SQL> create index t_test1_idx2 on t_test1(owner, created) compute statistics;
    
  
    
  
    
      Index created.
    
  
    
  
    
      SQL> select object_name
    
  
    
       2 from t_test1
    
  
    
       3 where owner = 'SYS'
    
  
    
       4 and created > sysdate - 30;
    
  
    
  
    
      no rows selected
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 3417015015
    
  
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)
    
  
    
      | Time |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | 0 | SELECT STATEMENT | | 49 | 1715 | 2 (0)
    
  
    
      | 00:00:01 |
    
  
    
      | 1 | TABLE ACCESS BY INDEX ROWID| T_TEST1 | 49 | 1715 | 2 (0)
    
  
    
      | 00:00:01 |
    
  
    
      |* 2 | INDEX RANGE SCAN | T_TEST1_IDX2 | 49 | | 1 (0)
    
  
    
      | 00:00:01 |
    
  
    
      --------------------------------------------------------------------------------
    
  
    
  
    
      Predicate Information (identified by operation id):
    
  
    
      ---------------------------------------------------
    
  
    
  
    
       2 - access("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
    
  
    
  
    
      ... ...
    
  

3.2.2.2 調整DB_FILE_MULTIBLOCK_READ_COUNT

當SQL已經沒有優化余地后,問題仍沒有解決,我們可以考慮調整DB_FILE_MULTIBLOCK_READ_COUNT大小。其作用我們在3.1.2中有做敘述,這里不再贅述。不過要注意一點就是,DB_FILE_MULTIBLOCK_READ_COUNT * DB_BLOCK_SIZE是一次IO讀取的傳輸量,它不能大于系統的max_io_size大小。

從Oracle 10gR2開始,如果沒有設置DB_FILE_MULTIBLOCK_READ_COUNT的大小,Oracle會自動為其調整一個默認值,這個默認值的大小與平臺最大IO大小(max_io_size)相關(對大多數平臺來說max_io_size是1M),其大小被設置為(max_io_size / DB_BLOCK_SIZE)。

3.2.2.3 將頻繁訪問的全掃描的表CACHE住

由于通過Full Table Scan和Fast Full Index Scan讀取的數據塊會被放置到Buffer Cache的LRU鏈表的LRU端,從而使數據塊盡快從Buffer Cache中移出。因此,對于那些會被頻繁訪問到全掃描的表,且其數據量不大的情況下,我們可以考慮將它們CACHE住。

    
      SQL> 
      
        alter table t_test1 cache;
      
    
  
    
  
    
      Table altered.
    
  

對于Fast Full Index Scan的索引對象,則可以考慮把它放置在KEEP池中。

    
      SQL> alter index t_test1_idx1 storage(buffer_pool keep);
    
  
    
  
    
      Index altered.
    
  

利用V$SESSION_EVENT視圖,我們同樣可以找到當前系統中發生全掃描的對象。

    
      SQL> select p1 "fileid", p2 "block_id", p3 "block_num"
    
  
    
       2 from v$session_wait
    
  
    
       3 where event = 'db file scattered read';
    
  
    
  
    
       fileid block_id block_num
    
  
    
      ---------- ---------- ----------
    
  
    
       359 152972 16
    
  
    
  
    
      SQL> select
    
  
    
       2 segment_name "Segment Name",
    
  
    
       3 segment_type "Segment Type",
    
  
    
       4 block_id "First Block of Segment",
    
  
    
       5 block_id+blocks "Last Block of Segment"
    
  
    
       6 from dba_extents
    
  
    
       7 where &fileid = file_id
    
  
    
       8 and &block_id >= block_id
    
  
    
       9 and &block_id <= block_id+blocks;
    
  
    
      Enter value for fileid: 359
    
  
    
      old 7: where &fileid = file_id
    
  
    
      new 7: where 359 = file_id
    
  
    
      Enter value for block_id: 152972
    
  
    
      old 8: and &block_id >= block_id
    
  
    
      new 8: and 152972 >= block_id
    
  
    
      Enter value for block_id: 152972
    
  
    
      old 9: and &block_id <= block_id+blocks
    
  
    
      new 9: and 152972 <= block_id+blocks
    
  
    
  
    
      Segment Name
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      Segment Type First Block of Segment Last Block of Segment
    
  
    
      ------------------ ---------------------- ---------------------
    
  
    
      CSS_TP_SHMT_QUEUE
    
  
    
      TABLE 152969 153001
    
  

3.2.2.4 利用分區表減少全掃描操作讀取的數據塊數量

前面我們有介紹分區裁剪(Partition Pruning)技術。將表分區,利用分區裁剪技術,在進行全掃描時只會掃描在WHERE條件中出現的分區,從而可以減少全掃描所讀取到的數據塊數量。

3.2.2.5 Housekeep歷史數據

同樣,housekeep不需要的、歷史的數據,減少數據段中的數據塊數量,也能減少全掃描的IO請求次數。

3.2.3 db file parallel read

首先,不要被該事件名稱所誤導——它和并行DML或者并行查詢都無關。當從多個數據文件并行讀取數據到非聯系的內存(PGA、Buffer Cache)緩沖中時,會發生該等待事件。它通常發生在Recovery操作或者利用緩沖預提取(Buffer Prefetching)從數據文件并行讀取數據時。

我們可以通過以下語句找出發生db file parallel read等待事件的數據文件和數據塊:

    
      select p1 "fileid", p2 "block_id", p3 "requests"
    
  
    
       from v$session_wait
    
  
    
       where event = 'db file parallel read';
    
  

優化該等待事件的手段可以參考優化db file sequential read等待事件中非SQL優化方法部分。

3.2.4 direct path read & direct path read (lob)

當直接讀取(Direct Read)數據到PGA(而不是到Buffer Cache)中去時,會發生Direct Path Read等待事件。對Lob數據的直接讀有一個單獨的等待事件——direct path read (lob)。

當Oracle設置支持異步IO時,進程可以在提交IO請求后繼續做其他操作,并且在稍后再提取IO請求返回的結果,在提取結果時就產生了direct path read等待事件。

在沒有啟用異步IO時,IO請求在完成之前會被阻塞,但在執行IO操作時并不會產生等待事件。進程稍后回來提取那些已經讀取到的IO數據,這時盡管能夠很快返回,但仍然會顯示direct path read等待事件。

和其他IO等待事件不同的是,對Direct Path Read等待事件要注意以下兩點:

  • 等待次數并不等于IO請求次數;
  • 統計(如statspack報告中)得出的Direct Path Read的等待時間并不一定代表該事件引起的真正等待時間。

事件中的P1、P2、P3參數分別代表:

P1:發生等待事件的數據塊所在文件號;

P2:發生等待事件的數據塊號;

P3:等待事件涉及的連續數據塊數量。

直接讀(Direct Read)請求一般發生在以下幾種情況:

  • 磁盤排序IO(Sort Area不足時,排序用到的臨時數據會被寫到臨時表空間上去,當讀取這些數據時就使用直接讀);
  • 并行查詢;
  • 預讀取(當一個進程認為某個數據塊將很快被用到而發出IO請求時)
  • Hash Join(Hash Area不足)
  • IO負載系統中,服務進程處理緩存的速度比系統IO返回數據到緩存的速度更快時

通過視圖V$SESSION_EVENT我們可以找出當前產生等待的會話,再根據會話中正在進行的操作確定導致等待的原因。針對不同的原因,我們可以采取不同的措施減少Direct Path Read等待事件。

3.2.4.1 磁盤排序

首先我們可以考慮優化語句以減少排序操作。排序一般是由以下操作引起的:

  • Order By;
  • JOIN;
  • UNION;
  • Group By;
  • 聚合操作;
  • Select unique;
  • Select distinct;

可以嘗試在語句中減少沒必要的上述操作來避免排序操作。另外,創建索引也會引起排序操作。在專業模式(Dedicated)下,排序所占用的內存是從PGA中分配出來的一塊區域,叫Sort Area,由參數sort_area_size控制其大小;在MTS中,排序區是從Large Pool中分配的。當sort area大小無法滿足排序操作要求時,就會占用臨時表空間來存放排序數據,因而產生Direct Path Read等待事件。我們可以通過適當增加該參數來減少磁盤排序操作。

這個參數可以在系統范圍或會話范圍進行修改。對于一些需要做大量排序操作而且又比較獨立的會話(如Create Index),我們可以在會話級別為其設置比較大的Sort Area以滿足排序需要:

    
      SQL> alter session set sort_area_size = 10000000;
    
  
    
  
    
      Session altered.
    
  

該參數大小一般推薦設置為1~3M。在9i之后,不推薦設置該參數,我們可以通過設置PGA_AGGREGATE_TARGET進行PGA內存自動管理(設置WORKAREA_SIZE_POLICY為TRUE)。對于PGA_AGGREGATE_TARGET的大小設置,可以參考文章《 Oracle內存全面分析 》中的PGA_AGGREGATE_TARGET部分。

此外,我們還可以通過以下語句來查找系統中存在磁盤排序的會話及其語句:

    
      SELECT a.sid,a.value, b.name, d.sql_text from

V$SESSTAT a, V$STATNAME b, V$SESSION c, V$SQLAREA d

WHERE a.statistic#=b.statistic#

AND b.name = 'sorts (disk)'

and a.sid = c.sid

and c.SQL_ADDRESS = d.ADDRESS(+)

and c.SQL_HASH_VALUE = d.HASH_VALUE(+)

and value > 0

ORDER BY 2 desc,1;
    
  

3.2.4.2 并行查詢

當設置表的并行度非常高時,優化器可能就對表進行并行全表掃描,這時會引起Direct Path Read等待。

在使用并行查詢前需要慎重考慮,因為并行查詢盡管能教師程序的響應時間,但是會消耗比較多的資源。對于低配置的數據庫服務器不建議使用并行特性。此外,需要確認并行度的設置要與IO系統的配置相符(建議并行度為2~4 * CPU數)。在10g中,可以考慮使用ASM。

對于表的并行度,我們不建議直接用ALERT修改表的物理并行度:

    
      ALTER TABLE t_test1 PARALLEL DEGREE 16;
    
  

而是推薦針對特定語句使用提示來設置表的并行度:

    
      SQL> SELECT /*+ FULL(T) PARALLEL(T, 4)*/ object_name FROM t_test1 t;
    
  
    
  
    
      47582 rows selected.
    
  
    
  
    
  
    
      Execution Plan
    
  
    
      ----------------------------------------------------------
    
  
    
      Plan hash value: 2467664162
    
  
    
  
    
      --------------------------------------------------------------------------------
    
  
    
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
    
  
    
      | TQ |IN-OUT| PQ Distrib |
    
  
    
      ---------------------------------------------------------------------------
    
    -----
  
    
      | 0 | SELECT STATEMENT | | 47582 | 1068K| 42 (3)| 00:00:01
    
  
    
      | | | |
    
  
    
      | 1 | PX COORDINATOR | | | | |
    
  
    
      | | | |
    
  
    
      | 2 | PX SEND QC (RANDOM)| :TQ10000 | 47582 | 1068K| 42 (3)| 00:00:01
    
  
    
      | Q1,00 | P->S | QC (RAND) |
    
  
    
      | 3 | PX BLOCK ITERATOR | | 47582 | 1068K| 42 (3)| 00:00:01
    
  
    
      | Q1,00 | PCWC | |
    
  
    
      | 4 | TABLE ACCESS FULL| T_TEST1 | 47582 | 1068K| 42 (3)| 00:00:01
    
  
    
      | Q1,00 | PCWP | |
    
  
    
      --------------------------------------------------------------------------------
    
  

3.2.4.3 Hash Join

Hash Area是用于hash join的內存區域。Hash Area過小會引起Direct Path Read等待。當WORKAREA_SIZE_POLICY為FALSE時,可以考慮增加hash_area_size的大小(建議為sort_area_size大小的1.5倍);當WORKAREA_SIZE_POLICY為TRUE時,可以考慮增加PGA_AGGREGATE_TARGET大小。

3.2.4.4 Direct path read (lob)

為了減少LOB的讀寫時間,通常我們會設置LOB的存儲參數NOCACHE,這時讀取LOB時會引起Direct Path Read (lob)等待事件。但當我們發現Direct path read (lob) 引起了IO性能問題,就需要考慮將那些被經常讀取的LOB字段設置為CACHE。另外,如果操作系統的文件系統有足夠的Buffer Cache時可以考慮將LOB數據段存儲在文件系統上。

3.2.4.5 其他優化措施

當內存資源不足、IO讀取數據到內存效率遠遠低于內存中數據被處理的效率時,會引起Direct Path Read等待事件。作為對上述處理措施的補充,增加內存(PGA)、在確保操作系統支持AIO情況下設置DISK_ASYNCH_IO為TRUE以支持異步IO、采用效率更高的存儲設備都能幫助我們減少Direct Path Read等待。

3.2.5 direct path write & direct path write (lob)

直接寫(Direct Path Write)允許一個會話先將IO寫請求放入一個隊列中,讓操作系統去處理IO,而自身可以繼續處理其他操作。當會話需要知道寫操作是否完成(如會話需要一塊空閑的緩存塊或者會話需要確認內存中所有寫操作都被flush到磁盤了),會話就會等待寫操作完成從而產生Direct Path Write等待事件。Direct Path Write (lob) 是在對LOB數據段(NOCACHE)直接寫時產生的等待事件。

在沒有啟用異步IO時,IO寫請求在完成之前會被阻塞,但在執行IO寫操作時并不會產生等待事件。進程稍后回來提取那些已經完成的IO操作數據,這時盡管能夠很快返回,但仍然會顯示direct path write等待事件。

和Direct Path Read等待事件相似,對Direct Path Write等待事件也要注意以下兩點:

  • 等待次數并不等于IO請求次數;
  • 統計(如statspack報告中)得出的Direct Path Write的等待時間并不一定代表該事件引起的真正等待時間。

事件中的P1、P2、P3參數分別代表:

P1:發生等待事件的數據塊所在文件號;

P2:發生等待事件的數據塊號;

P3:等待事件涉及的連續數據塊數量。

直接寫請求一般發生在以下幾種情況:

  • 直接數據載入操作(如CTAS、SQL*Loader設置Direct選項等);
  • 并行DML操作;
  • 磁盤排序(排序內存空間不足,數據寫入磁盤);
  • 載入NOCACHE數據段;

對Direct Path Write的優化處理措施基本上和Direct Path Write類似。

3.5 Buffer Cache相關的IO事件

Buffer Cache是影響Oracle IO的重要因素。這里要解決的幾個等待事件都是涉及到DBWR進程和IO從屬進程(Slave)的Buffer Cache操作引起的等待事件。

3.5.1 db file parallel write

該事件和并行DML無關。這個等待事件出現在當DBWR進程提交了多IO請求來并行將Buffer Cache中的臟數據寫入磁盤中后,等待所有提交的IO請求完成。通常是由于操作系統的IO系統導致的該事件的阻滯。

事件中的P1、P2、P3參數分別代表:

P1:(9.2.0.5之前)寫入數據的文件號/(9.2.0.5之后)請求次數;

P2:(9.2.0.5之前)寫入的數據塊號/(9.2.0.5之后)請求中斷的次數;

P3:(9.2.0.5之前)請求次數/(9.2.0.5之后)請求發生了Timeout的時間

這一等待事件一般不會顯著影響用戶會話。但是當用戶會話中有很高的“write complete waits”或“free buffer waits”事件的等待時間時,說明該事件已經影響到了用戶會話。有時候這一事件對操作系統IO的影響也會影響到進程從同一磁盤讀取數據的等待時間。

解決該事件的關鍵在于減少相關磁盤的IO沖突。如果這事件已經影響到用戶會話,我們需要結合其他等待事件信息,考慮采取均衡熱地磁盤負載、提高存儲設備IO效率、增加checkpoint間隔、增大Redo log文件等方法來減低該事件。

3.5.2 db file single write

當DBWR進程請求修改數據文件頭,在等待IO請求完成時,會出現db file single write等待事件。

事件中的P1、P2、P3參數分別代表:

P1:寫入數據的文件號;

P2:寫入的數據塊號;

P3:寫入的數據塊數(一般為1)。

解決這一等待事件的關鍵還是要處理好磁盤的IO沖突問題,特別是發生該事件所在的磁盤。通過相關SQL的調優等手段來降低事件發生的磁盤的IO、采用更高效率的存儲設備、均衡磁盤IO負載等方法是降低這一等待事件的主要方法。

3.5.3 write complete waits

當會話對一個正在被寫寫入磁盤的Buffer數據塊發出請求時,需要等待其被寫入磁盤完成,這時就會產生write complete waits等待事件。

事件中的P1、P2、P3參數分別代表:

P1:要寫入數據的文件號;

P2:要寫入的數據塊號;

P3:無意義

提高Buffer Cache臟數據寫入磁盤的效率、提高整體IO效率是降低該等待事件的主要方法:

  • 配置數據庫支持AIO;
  • 增加db_writer_processes(支持AIO時)或者db_io_slaves(不支持AIO時)大小以增加DBWR進程;
  • 其他提高IO效率(如采用裸設備等)、減少IO沖突的方法

3.5.4 free buffer waits

當會話在Buffer Cache中找不到空閑buffer塊,或者在沒有空閑buffer塊來建立一致性讀時,就會產生free buffer waits等待事件。

事件中的P1、P2、P3參數分別代表:

P1:要讀取數據的文件號;

P2:要讀取的數據塊號;

P3:10gR1之前無意義,10gR1后表示在Buffer Cache中LRU和LRUW列表的SET_ID#

這一等待事件通常表示Buffer Cache不足或者從Buffer Cache中將臟數據寫入磁盤的效率太低。要降低該等待事件,我們就需要分別從這兩方面入手:調整Buffer Cache的大小(如根據statspack的建議器來設置);按照我們前述的方法來提高存儲設備的IO效率。

4 結束語

最后要說的是,一旦數據庫服務器出現了IO問題后,首先要檢查操作系統本身的IO系統是否有問題,然后再確認是否是Oracle出現了IO問題。

其次要注意的一點是,上述等待事件在系統出現一定的等待次數對于系統來說是正常的,我們要解決的是對系統IO影響最大的一個或幾個等待事件,而不是全部事件。

總的來說,要調整Oracle中出現的IO性能問題,我們有兩種手段:一種是針對特定等待事件的相應方法,如相關SQL語句的調優、相關參數的修改;另外一種是通過提升整體IO效率、減少IO沖突來降低IO等待,如均衡IO負載、使用效率更高的存儲設備、激活AIO和重新分布對IO有不同要求的文件。

事實上,數據庫的性能問題大多數是由應用引起的,而其中大部分問題都是Top SQL造成。因此,這里要說的一句題外話就是:SQL調優是每一個DBA必須具備的最基本的技能。因為很多時候無論采用什么手段、什么工具來定位問題,通過各種內部機制來分析問題,但最終解決問題的手段就是SQL調優。

5 參考文章

1、 www.HelloDBA.com

2、 Metalink.Oracle.com

3、 Oracle OTN

4、 Oracle Concept

5、 Oracle Database Performance Tuning Guide

Oracle中IO


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日韩久久一区二区三区 | 国产精品久久久久影视青草 | 七次郎在线成人精品 | 波多野结衣一二区 | 香蕉久久精品国产 | 欧美高清一区二区三区欧美 | 日日艹夜夜艹 | 97av在线视频 | 中文在线播放 | 日本亚洲精品久久 | 国产第8页| 欧美第一区 | 天天撸夜夜操 | 综合激情网站 | 国产精品视频在线播放 | 国产福利在线看 | 日本又黄又爽又色的视频免费 | 91av中文| 久久精品亚洲日本筱田优 | 欧美高清不卡午夜精品免费视频 | 欧美日韩高清在线观看一区二区 | 日本亚州在线播放精品 | 一区二区中文字幕在线观看 | 亚洲一区二区三区免费观看 | 精品在线免费视频 | 中国女人69xxxxx| 极品毛片 | 久久久久在线视频 | 57pao一国产成视频永久免费 | 国产成人免费全部网站 | 久久www免费人成_看片高清 | 麻豆成人在线 | 免费网站啪啪大全 | 欧美日本免费观看αv片 | 亚洲精品tv久久久久久久久久 | 久久精品国产一区二区三区肥胖 | 亚洲人成影院在线高清 | 日本精品视频一区二区三区 | 亚洲精品成人一区二区www | 中国性猛交xxxxx免费看 | 九一视频在线 |