http://www.hellodba.com/Doc/oracle_IO(7).htm
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請求次數。