Optimizer統計資訊管理介紹
1. 前言
在我們的日常維護中受理一些一直以來執行得很好的系統,突然有一天使用者反饋沒有做任何操作,系統的某個功能模組或者是某個報表以前只需要幾秒,但現在需要幾分鐘或更長的時間都沒有返回結果。在這樣的情況下,我們通常會分析SQL語句,會發現這個SQL的執行計劃已經發生改變,在硬體環境未發生變化的情況下,執行計劃發生變化多數原因是由於表的統計資訊發生了變化,原本使用的某個索引突然間沒有被使用,或者使用了較差的索引,這都是由於統計資訊不準備確引起的,因此我們有必要了解統計資訊的管理和維護,更好的優化SQL和效能問題處理。
統計資訊是描述該資料庫中的資料和資料庫中的物件的集合,這些統計資料所使用的優化選擇對每個
Oracle 10g之後,Query Optimizer就已經將CBO作為預設優化器,並且Oracle官方不再支援10G以前的RBO服務。但是,通過優化器引數OPTIMIZER_MODE,我們可以控制Oracle優化器生成不同模式下的執行計劃。資料庫中的物件可以是不斷變化的,統計資料必須定期更新,以便準確地描述這些資料庫物件,統計都是由Oracle自動地或手動使用
圖1儲存統計資訊字典檢視
表統計資訊包括表中行的數量資訊,表使用資料塊的數量,以及行在表中的平均行長度。優化器使用這些資訊,結合其它統計資訊,以計算各種操作的執行計劃成本,並估計行的操作將產生的數量。例如,一個表存取的成本是使用資料塊和引數DB_FILE_MULTIBLOCK_READ_COUNT的值進行計算,表的統計資訊可以在字典檢視
列統計資訊包括列上不同值的數量,以及在該列中的最小和最大值。可以在字典檢視USER_TAB_COL_STATISTICS檢視列統計資訊。優化器使用列統計資訊和表統計資訊的行數估計SQL操作返回的結果。例如,如果一個表有100條記錄,並且訪問表列有10個不同的相等謂詞,那麼優化器,假定資料分佈是均勻的,估計的基數為表中的行數不同的值除以該列,即100 / 10 = 10。
圖2使用基本表和列統計基數計算
索引統計資訊提供索引中不同值的資料資訊,葉塊中的索引數,索引的深度和叢集因子數目的數量。優化程式會使用這些資訊連同其他統計資訊來確定索引訪問的成本。例如優化器使用B樹索引,將會使用葉塊數和表統計中的num_rows確定索引範圍掃描的成本。
直方圖是一種對被管理物件某一方面質量進行管理的描述工具,在Oracle中自然它也是對Oracle中某個物件質量的描述工具,這個物件就是Oracle中最重要的東西——“資料”。
在Oracle中直方圖是一種對資料分佈質量情況進行描述的工具。它會按照某一列不同值出現數量多少,以及出現的頻率高低來繪製資料的分佈情況,以便能夠指導優化器根據資料的分佈做出正確的選擇。在某些情況下,表的列中的數值分佈將會影響優化器使用索引還是執行全表掃描的決策。當WHERE子句的值具有不成比例數量的數值時,將出現這種情況,使得全表掃描比索引訪問的成本更低。這種情況下如果WHERE子句的過濾謂詞列之上有一個合理的,正確的直方圖,將會對優化器使用索引還是全表掃描發揮巨大的作用,使得SQL語句執行成本最低從而提升效能。
在分析表或索引時,直方圖用於記錄資料的分佈。通過獲得該資訊,基於成本的優化器就可以決定使用將返回少量行的索引,而避免使用基於限制條件返回許多行的索引。直方圖的使用不受索引的限制,可以在表的任何列上構建直方圖。
構造直方圖最主要的原因就是幫助優化器在表中資料嚴重偏斜時做出更好的規劃:例如,如果一到兩個值構成了表中的大部分資料(資料偏斜),相關的索引就可能無法幫助減少滿足查詢所需的I/O數量。建立直方圖可以讓基於成本的優化器知道何時使用索引才最合適,或何時應該根據WHERE子句中的值返回表中80%的記錄。
通常情況下在以下場合中建議使用直方圖:
1) 當Where子句引用了列值分佈存在明顯偏差的列時:當這種偏差相當明顯時,以至於 WHERE子句中的值將會使優化器選擇不同的執行計劃。這時應該使用直方圖來幫助優化器來修正執行路徑。(注意:如果查詢不引用該列,則建立直方圖沒有意義)
2) 當列值導致不正確的判斷時:這種情況通常會發生在多表連線時,例如,假設我們有一個五項的表聯接,其結果集只有 10行。Oracle 將會以一種使第一個聯接的結果集(集合基數)儘可能小的方式將表聯接起來。通過在中間結果集中攜帶更少的負載,查詢將會執行得更快。為了使中間結果最小化,優化器嘗試在 SQL執行的分析階段評估每個結果集的集合基數。在偏差的列上擁有直方圖將會極大地幫助優化器做出正確的決策。如優化器對中間結果集的大小做出不正確的判斷,它可能會選擇一種未達到最優化的表聯接方法,因此向該列新增直方圖經常會向優化器提供使用最佳聯接方法所需的資訊。
Oracle利用直方圖來提高非均勻資料分佈的選擇率和技術的計算精度。但是實際上Oracle會採用另種不同的策略來生成直方圖:其中一種是針對包含很少不同值的資料集;另一種是針對包含很多不同的資料集。Oracle會針對第一種情況生成頻率直方圖,針對第二種情況生成高度均衡直方圖。通常情況下當BUCTET < 表的NUM_DISTINCT值得到的是HEIGHT BALANCED(高度平衡)直方圖,而當BUCTET > 表的NUM_DISTINCT值的時候得到的是FREQUENCY(頻率)直方圖。
Oracle 建議啟用自動優化程式統計資訊收集。在這種情況下,資料庫會自動收集沒有被收集過的統計資訊或過時的統計資訊。如果一個新表需要收集統計資訊,資料庫會收集表和關聯的索引。
自動優化統計資訊收集程式執行DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC呼叫,資料統計資訊收集過程DBMS_STATS.GATHER_DATABASE_STATS,該過程使用GATHER AUTO選項收集資料庫表的資訊和索引資訊,使統計資訊為最新狀態。GATHER_DATABASE_STATS_JOB_PROC過程進行統計資訊收集,過程會優先資料庫物件統計收集,所以在收集統計資訊期間要對資料庫的物件更新操作時,需要關閉自動統計收集作業。
Oracle自動維護任務基礎結構(稱為 AutoTask)排程程式會在維護視窗中自動執行該任務。預設情況下,每週六的晚上,自動優化程式統計資訊收集作為 AutoTask 的一部分執行,預設啟用的在所有的預定義的維護視窗中執行。
如果對於一些原因自動優化程式統計資訊收集被禁用,可以使用DBMS_AUTO_TASK_ADMIN包手動啟用:
BEGIN
DBMS_AUTO_TASK_ADMIN.ENABLE(
client_name => 'auto optimizer stats collection'
, operation => NULL
, window_name => NULL
);
END;
/
如果採用手動管理方式收集統計資訊,同樣可以使用可以使用DBMS_AUTO_TASK_ADMIN包手動禁用作業:
BEGIN
DBMS_AUTO_TASK_ADMIN.DISABLE(
client_name => 'auto optimizer stats collection'
, operation => NULL
, window_name => NULL
);
END;
/
對於不斷變化的資料庫物件,必須定期收集統計資料以便他們準確地描述資料庫物件。Oracle推薦使用DBMS_STATS過程包收集統計資訊,並取代現在已經過時的統計資訊收集命令ANALYZE 。DBMS_STATS 包包含超過 50 不同的過程,用於收集和管理統計,而且最重要的過程是 GATHER_ * _STATS 程式。這些過程可以用於收集和管理表、列和索引的統計資訊,必須使用物件的所有者或有任何具有系統特權的DBA 角色執行這些程式,以下是DBMS_STATS包中收集統計資訊涉及到的過程。
名稱 |
用途描述 |
GATHER_INDEX_STATS |
收集特定使用者下指定索引列的統計資訊 |
GATHER_TABLE_STATS |
收集特定使用者指定表上表行,列和索引列的統計資訊 |
GATHER_SCHEMA_STATS |
收集特定使用者所有對像的統計資訊 |
GATHER_DICTIONARY_STATS |
收集資料庫所有資料字典統計資訊 |
GATHER_DATABASE_STATS |
收集資料庫所有物件統計資訊 |
這些程式使用的引數是幾乎相同,因此這裡列舉 GATHER_TABLE_STATS 過程的引數作為說明,GATHER_TABLE_STATS包過程用於收集表,分割槽,索引和列的統計資訊。這個過程擁有15個不同的引數。我們在收集表的統計資訊時,只需指定ownname和tabname這兩個引數,過程包就可以執行。如果表是分割槽表還需要指分割槽名稱。例如我們對住院費用記錄表進行統計資訊進行收集,使用以下方式就可以收集到該表的統計資訊。
SQL> begin
2 dbms_stats.gather_table_stats(ownname => 'ZLHIS',
3 tabname => '藥品庫存');
4 end;
5 /
PL/SQL procedure successfullycompleted
在進行資料採集時我們有時會使用到其它輸入引數,在這裡我們對過程中的其它輸入引數介紹。
l ESTIMATE_PERCENT
ESTIMATE_PERCENT引數確定用來計算統計資訊行數的百分比,最準確的統計資訊收集處理是收集表中的所有行。Oracle 11g使用一種新的取樣演算法,基於雜湊值並提供準確的統計資訊。這種新方法精度接近所有行(100%)樣品,但頂多消耗10%樣品的成本。ESTIMATE_PERCENT的預設值設定為AUTO_SAMPLE_SIZE,將使用這種新演算法。GATHER_ * _STATS 程式。我們在對ZLHIS物件收集統計資訊時,將ESTIMATE_PRECENT引數設定為較低的值,通常是10%的方式收集,這樣做以確保將收集統計資料的結果迅速。當然為了資料庫得到更準備統計資訊。Oracle強烈建議從 Oracle 11g 起使用ESTIMATE_PRECENT引數的預設值收集統計資訊,該引數的取值範圍為取值範圍[0.000001-100]。
示例:引數ESTIMATE_PERCENT=10以病人醫囑傳送資料表資料10%的比例進行資料收集。
begin
dbms_stats.gather_table_stats(ownname => 'ZLHIS',
tabname => '病人醫囑傳送',
estimate_percent=> 10,
method_opt => 'for all columns size skewonly',
force => true,
cascade => true,
degree => 4);
end;
l METHOD_OPT
這個引數最常見的功能就是控制直方圖的收集方式,但實際上它的功能遠不及此,它的實際功能如下所示:
ü 控制哪些列收集基本的統計資訊
ü 收集直方圖,
ü 收集擴充套件的統計資訊
Method_opt 引數用法分為兩個部分,如下圖所示:
FOR ALL [indexed | hidden] columns這一部分控制著哪些列將會收集列的基本統計資訊,目標列上的最小值,最大值,列上不同值的數量,空值的數量等等。系統預設值為FOR ALL COLUMNS,它將收集表上所有列(包括隱藏列)的基本的統計資訊。指定FOR ALL INDEXED COLUMNS 只收集含有索引欄位列的基本統計資訊。一般不推薦使用這個選項值,因為在資料庫環境中的所有 SQL語句所使用的欄位,比如SELECT 後面的欄位,WHERE後面欄位,GROUP BY中的欄位,並不只是會引用含有索引的欄位。指定FOR ALL HIDDEN COLUMNS收集所有不可見欄位基本統計資訊,同樣在收集統計資訊時不推薦使用這個選項值。這個選項值通常只用於在一個所有列的統計資訊都是準確的表中新增了一個或幾個不可見或者說是虛擬的列,只需要收集這個或者這幾個不可見列的統計資訊,而不再重複去其他列的統計資訊。
Size [size_clause]這一部分控制收集直方圖的方式,SIZE 後面可以有以下選項:
AUTO Oracle自己決定根據列的統計資訊(sys.col_usage$)以及列的資料傾斜程度(均勻分佈程度)決定哪些列需要收集直方圖。
INTEGER指定收集直方圖的桶數,桶數最小為 1最大為 254 (針對 11g及以前的版本, 12c後沒有這個限制)。注意如果桶數為 1,即SIZE 1 意味著不建立直方圖,如果已經有直方圖的列則會刪除該列的直方圖。
REPEAT只在已經有直方圖的列上重新收集直方圖。REPEAT會確保在全域性級別上對已經存在直方圖的列重新收集直方圖。一般不推薦使用這個選項,因為新的直方圖使用的桶數將不能超過舊的直方圖中的桶數。假設當前直方圖中桶數為 5,當使用SIZE REPEAT重新收集直方圖時,新的直方圖使用的桶數將不能超過 5,這鐘方式可能不會取得好的效果。
SKEWONLY 只在資料不均勻分佈的列上收集直方圖。
示例:引數METHOD_OPT = 'FOR ALL COLUMNS SIZESKEWONLY'收集病人醫囑分佈不均勻列的直方圖統計資訊。
begin
dbms_stats.gather_table_stats(ownname => 'ZLHIS',
tabname => '病人醫囑傳送',
estimate_percent => 10,
method_opt => 'for all columns size skewonly',
force => true,
cascade => true,
degree => 4);
end;
l DEGREE
DEGREE引數控制伺服器並行收集統計資料的程序數。預設情況下,Oracle資料庫中的所有表的DEGREE屬性為1,我們可以更改這個引數值,加快統計資料的收集。當DEGREE設定值為DMBS_STATS.AUTO_DEGREE,Oracle根據並行伺服器程序數引數(PARALLEL_MAX_SERVERS)值自動分配程序數收集統計資訊。對一個數據量較小的物件,使用預設值1即可。對大資料物件的可以使用DBMS_STAT.DEFAULT_DEGREE引數由資料庫自動分配並行度。
示例:DEGREE=4以4個程序收集病人醫囑傳送記錄的統計資訊。
begin
dbms_stats.gather_table_stats(ownname => 'ZLHIS',
tabname => '病人醫囑傳送',
estimate_percent => 10,
method_opt => 'for all columns size skewonly',
force => true,
cascade => true,
degree => 4);
end;
l CASCADE
在系統上沒有執行過索引統計資訊收集。使用CASCADE選項相當於在除了收集表和列統計並同時執行 GATHER_INDEX_STATS收集索引統計資訊,使用引數DBMS_STATS.AUTO_CASCADE由ORACLE確定是否收集索引統計資訊要,引數設定為TRUE強制收集所有索引統計資訊,在預設情況下CASCADE引數值為FALSE。
示例:強制收集病人醫囑傳送上的所有索引統計資訊。
begin
dbms_stats.gather_table_stats(ownname => 'ZLHIS',
tabname => '病人醫囑傳送',
estimate_percent => 10,
method_opt => 'for all columns size skewonly',
force => true,
cascade => true,
degree => 4);
end;
在某些情況下我們需要鎖定一個特定表的統計息不被更新,以保證執行計劃的準確性,我們需要使用DBMS_STATS.LOCK_TABLE_STATS鎖定統計資訊。要鎖定一個表的統計資訊我們只需要傳入表的擁有者和表名就可以鎖定該使用者表的統計資訊。反之我們要解鎖一個鎖定的使用者物件使用DBMS_STAT.UNLOCK_TABLE_STATS解鎖鎖定的統計資訊。
示例:鎖定ZLHIS使用者下藥品收發記錄統計資訊。
begin
DBMS_STATS.LOCK_TABLE_STATS(ownname =>'ZLHIS' , tabname =>'藥品收發記錄' );
end;
示例:解鎖被鎖定的ZLHIS使用者下藥品收發記錄統計資訊。
begin
DBMS_STATS.UNLOCK_TABLE_STATS(ownname =>'ZLHIS' , tabname =>'藥品收發記錄' );
end;
有的時候我們在收集了統計資訊卻發現新的統計資訊比舊的統計資訊還要差,因此我們需要將統計資訊刪除重新收集,以下列出刪除統計資訊相關過程。
名稱 |
用途描述 |
DELETE_INDEX_STATS |
刪除特定使用者下指定索引列的統計資訊 |
DELETE_TABLE_STATS |
刪除特定使用者指定表上表行,列和索引列的統計資訊 |
DELETE _SCHEMA_STATS |
刪除特定使用者所有對像的統計資訊 |
DELETE _DICTIONARY_STATS |
刪除資料庫所有資料字典統計資訊 |
DELETE _DATABASE_STATS |
刪除資料庫所有物件統計資訊 |
示例:刪除ZLHIS使用者藥品收發記錄表統計資訊
begin
DBMS_STATS.delete_table_stats(ownname =>'ZLHIS' , tabname =>'藥品收發記錄' )
end;
所有與資料表,列和索引相關的統計資訊都可以通過Oracle資料庫字典檢視查詢,常使用的檢視有:
名稱 |
用途描述 |
DBA_TAB_COLUMNS ALL_TAB_COLUMNS USER_TAB_COLUMNS |
DBA檢視描述資料庫中所有表列。使用者檢視僅限於由使用者擁有的表,在這些檢視中統計資訊有關的列由DBMS_STATS 包或ANALYZE語句生成的統計資訊。 |
DBA_TAB_STATISTICS ALL_TAB_STATISTICS USER_TAB_STATISTICS |
描述表的統計資訊 |
DBA_INDEXES ALL_INDEXES USER_INDEXES |
DBA 檢視描述在資料庫中的所有表上的索引。所有檢視都描述在使用者可訪問的所有表上的索引。使用者檢視僅限於由使用者擁有的索引。在這些檢視中的統計資訊有關的列包含由DBMS_STATS 包或ANALYZE語句生成的統計 |
在這裡我們使用USER_TAB_STATISTICS檢視為例,先了解檢視USER_TAB_STATISTICS欄位的含義後,使用該檢視查看錶藥品收發記錄統計資訊。
USER_TAB_STATISTICS
例名 |
描述 |
TABLE_NAME |
表名 |
PARTITION_NAME |
分割槽表名 |
PARTITION_POSITION |
分割槽位置 |
SUBPARTITION_NAME |
子分割槽表名 |
SUBPARTITION_POSITION |
子分割槽位置 |
OBJECT_TYPE |
物件型別(表,分割槽,子分割槽) |
NUM_ROWS |
物件中的行記錄數 |
BLOCKS |
物件使用的資料塊數 |
EMPTY_BLOCKS |
物件中的空塊數 |
AVG_SPACE |
物件中的平均可用空間 |
CHAIN_CNT |
物件中的行連線數 |
AVG_ROW_LEN |
物件中行記錄的平均長度 |
AVG_SPACE_FREELIST_BLOCKS |
在一個自由列表的所有塊的平均可用空間 |
NUM_FREELIST_BLOCKS |
在一個自由列表的塊的數量 |
AVG_CACHED_BLOCKS |
在緩衝區快取記憶體中的平均塊數 |
AVG_CACHE_HIT_RATIO |
平均快取物件的命中率 |
SAMPLE_SIZE |
取樣樣本 |
LAST_ANALYZED |
最後一次表分析時間 |
GLOBAL_STATS |
沒有合併的分割槽計算的統計? |
USER_STATS |
統計資訊是否為使用者輸入 |
STATTYPE_LOCKED |
鎖定統計資訊型別 |
STALE_STATS |
統計資訊是否過期 |
例如,我們現在查詢藥品收發記錄的相關統計資訊的行數,表分析的取樣樣本,統計資訊是否被鎖定,統計資訊是否過期這幾種情況,可以使用欄位NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STATTYPE_LOCKED,STALE_STATS獲取相關資訊。
4. 結束語
通過我們對統計的瞭解,已經知道統計資訊對Oracle是非常重要的,它會收集資料庫中物件的詳細資訊,並存儲在相應的資料字典裡。根據這些統計資訊,優化器可以對每個SQL去選擇最好的執行計劃,統計資訊收集作業由Oracle定期自動收集,但某些特殊情況下還需要我們進行手動維護和管理,例如我們產品升級後及時手動採集資料庫的統計資訊是非常有必要的。