1. 數據庫優化基本知識
I/O
數據庫的基本作用就是實現對數據的管理與查詢。隨之而來的就是大量的IO操作, 在海量數據的情況下,數據庫的性能問題有80%以上和IO有關。優化ORACLE數據庫的I/O性能一般有兩個方面,一是減少處理時間,二是減少等待事件。
數據塊
oracle每次執行i/o時候就是以oracle塊爲單位。數據庫的邏輯結構包括:數據塊,區,段,表空間,數據塊是數據庫存儲基礎,是數據庫的最小邏輯單元。默認oracle塊大小是8k。oracle塊是處理update、insert、select數據事務的最小單位,當用戶從表中選擇數據時,將在oracle塊上讀取數據。
數據塊結構
1、基本組成
塊頭:存放一些基本信息,如物理位置,塊所屬的段類型(數據段、索引段、回滾段等)
表目錄:如果塊中存儲的數據爲表數據,則表目錄中保存這個表的相關信息
行目錄:如果塊中存儲的數據爲表數據,則行目錄中保存數據行的相關信息。
行記錄:真正存放數據的區域,這部分空間已被使用。
自由空間:未使用的區域,用於新行的插入或者已經存在行的更新。
2、自由空間(free space) (主要用於insert、update時使用)
Insert、update的時候塊的自由空間也會減少。當使用DELETE語句刪除塊中的記錄或者使用UPDATE語句把列的值更改成一個更小值的時候,Oracle會釋放出一部分自由空間。當insert或update的值超出了自由空間的大小則會發生行遷移或者行連接。
l 行鏈接
當第一次插入行時,由於行太長而不能容納在一個數據塊中時,就會發生行鏈接。在這種情況下,oracle會先把自由空間使用與該塊鏈接的一塊或多塊數據塊來容納該行的數據。行連接經常在插入比較大的行時纔會發生,如包含long, long row, lob等類型的數據。在這些情況下行鏈接是不可避免的。導致了在一次讀取過程中要讀取多個數據塊,引起I/O性能下降。
l 行遷移
當update後的行長度大於修改前的行長度,並且該數據塊中的自由空間已經比較小而不能完全容納該行的數據時,就會發生行遷移。在這種情況下,Oracle會將整行的數據遷移到一個新的數據塊上,而將該行原先的空間只放一個指針,指向該行的新的位置,注意,即使發生了行遷移,發生了行遷移的行的rowid 還是不會變化,這也是行遷移會引起數據庫I/O性能降低的原因。
參考資料:http://docs.oracle.com/cd/E11882_01/server.112/e40540/logical.htm#CNCPT1063
用analyze分析發生行遷移、行連接的行。
3、自由空間的管理
對於塊中的自由空間,Oracle提供兩種管理方式:自動管理,手動管理。
1)Oracle使用位圖(bitmap)來管理和跟蹤數據塊,這種塊的空間管理方式叫“自動管理”。自動管理有下面的好處:
◆易於使用
◆更好地利用空間
◆可以對空間進行實時調整
2)塊中自由空間的手動管理(手動管理比較複雜)
用戶可以通過PCTFREE, PCTUSED來調整塊中空間的使用,這種管理方式叫手動管理。一般調PCTFREE,相對於自動管理,手動管理方式比較麻煩,不容易掌握,容易造成塊中空間的浪費。
PCTFREE參數用於指定塊中必須保留的最小空閒空間百分例。之所以要預留這樣的空間,是因爲UPDATE時,需要這些空間。如果UPDATE時,沒有空餘空間,Oracle就會分配一個新的塊,這會產生行遷移(Row Migrating)。
Select table_name,pct_free fromuser_tables
PCTUSED也是用於設置一個百分比,當塊中已使用的空間的比例小於這個百分比的時候,這個塊才被標識爲有效狀態。只有有效的塊才被允許插入數據。
sql 的解析過程
1) 運用HASH算法,得到一個HASH值,這個值可以通過V$SQLAREA.HASH_VALUE 查看
2) 到shared pool 中的 library cache 中查找是否有相同的HASH值,如果存在,則無需硬解析,進行軟解析
3) 如果shared pool不存在此HASH值,則進行語法檢查,查看是否有語法錯誤
4) 如果沒有語法錯誤,就進行語義檢查,檢查該SQL引用的對象是否存在,該用戶是否具有訪問該對象的權限
5) 如果沒有語義錯誤,對該SQL進行解析,生成解析樹,執行計劃
6) 生成ORACLE能運行的二進制代碼,運行該代碼並且返回結果給用戶
硬解析和軟解析都在第5步進行
硬解析通常是昂貴的操作,大約佔整個SQL解析過程的70%左右的時間,硬解析會生成執行樹,執行計劃,等等。
當再次執行同一條SQL語句的時候,由於發現library cache中有相同的HASH值,這個時候不會硬解析,而會軟解析, 其實軟解析就是跳過了生成解析樹,生成執行計劃這個耗時又耗CPU的操作,直接利用生成的執行計劃運行該SQL語句。
執行以下4個sql,觀察生成硬解析與執行計劃的情況
Sql1:select *from emp where empno=7788;
Sql2:select *from Emp where empno=7788;
Sql3:select *from emp where empno=7788;
Sql4:select *from emp where empno=7788;
執行結果如下,可以看到4句sql由於1與4完全相同,則發生了一次硬解析一次軟解析,執行次數爲2並且使用了同一個執行計劃。2與3由於一點細微的改變則各自硬解析了一次並且各自重新生成了一個執行計劃,即便執行計劃是相同的。
SQL_TEXT | SQL_ID | PARSE_CALLS | HASH_VALUE | EXECUTIONS | PLAN_ADDRESS | PLAN_HASH_VALUE |
select * from Emp where empno=7788 | gmjqmqnsyufus | 1 | 837630808 | 1 | 31EDE2C8 | 2113372157 |
select * from emp where empno=7788 | 2cv6qqj01b9wu | 2 | 1075160986 | 2 | 31EEE1AC | 2113372157 |
select * from emp where empno=7788 | 3kpy13pt6qy2f | 1 | 1919645774 | 1 | 31EDEA10 | 2113372157 |
總結
降低HWM:儘量用TRUNCATE代替DELETE、重構表
減少因內存不足導致的等待:減少union、distinct、減少orderby,這些佔內存
減少網絡傳輸等待:減少dblink,將要訪問的遠程的表接過來一次,以後直接訪問這個表
2. 執行計劃(數據庫訪問數據的路徑)
執行計劃是一個很複雜的課題,這裏根據本輪優化來簡單介紹一下如何使用執行計劃進行優化。
執行計劃簡單的講就是數據庫如何訪問數據的路徑,從數據庫訪問到一條數據的方法有多種,執行計劃會從衆多方案中通過各種比較選出開銷最小(CBO模式)一個訪問路徑,它會因很多因素的改變受到影響。
oracle訪問數據的方法
1) 全表掃描(Full Table Scans,FTS) (掃描表的所有塊)
爲實現全表掃描,Oracle讀取表中所有的行,並檢查每一行是否滿足語句的WHERE限制條件一個多塊讀操作可以使一次I/O能讀取多塊數據塊(db_block_multiblock_read_count參數設定),而不是隻讀取一個數據塊,這極大的減 少了I/O總次數,提高了系統的吞吐量,所以利用多塊讀的方法可以十分高效地實現全表掃描,而且只有在全表掃描的情況下才能使用多塊讀操作。在這種訪問模式下,每個數據塊只被讀一次。
使用FTS的前提條件:在較大的表上不建議使用全表掃描,除非取出數據的比較多,超過總量的5% —— 10%,或你想使用並行查詢功能時。
2) 通過ROWID的表存取(Table Access by ROWID或rowid lookup)
行的ROWID指出了該行所在的數據文件、數據塊以及行在該塊中的位置,所以通過ROWID來存取數據可以快速定位到目標數據上,是Oracle存取單行數據的最快方法。
這種存取方法不會用到多塊讀操作,一次I/O只能讀取一個數據塊。我們會經常在執行計劃中看到該存取方法,如通過索引查詢數據。
3) 索引掃描(Index Scan或index lookup)(索引也是放在數據塊上的,執行索引時先從數據塊上找到索引,再根據索引找到數據所在的數據塊,比 ROWID多了一步)
我們先通過index查找到數據對應的rowid值(對於非唯一索引可能返回多個rowid值),然後根據rowid直接從表中得到具體的數據,這 種查找方式稱爲索引掃描或索引查找(index lookup)。一個rowid唯一的表示一行數據,該行對應的數據塊是通過一次i/o得到的,在此情況下該次i/o只會讀取一個數據庫塊。
在索引中,除了存儲每個索引的值外,索引還存儲具有此值的行對應的ROWID值。有4種類型的索引掃描
(1)索引唯一掃描(index unique scan)
(2)索引範圍掃描(index range scan):在非唯一索引上都使用索引範圍掃描。使用index rang scan的3種情況:
(a)在唯一索引列上使用了range操作符(><<>>=<= between)
(b)在組合索引上,只使用部分列進行查詢,導致查詢出多行
(c)對非唯一索引列上進行的任何查詢。
(3)索引全掃描(index full scan)(count某一列時)
(4)索引快速掃描(index fast full scan)
每種訪問方式都有其特定使用場景,如果在一個場景下出現了不適合的訪問方式很有可能會引起效率的下降,這也是優化中主要的優化原則。
Oracle連接方法
連接發生在一對錶或數據行源之間,當在from子句中存在多張表時,優化器將決定哪種連接運算對於每一張表來說效率最高。
常見連接方法:嵌套循環連接、散列連接、排序-合併及笛卡爾,每種方法都有一定的適合條件。每個連接方式都有兩個分支,訪問的第一張表叫驅動表,訪問的第二張表叫被驅動表,通常優化器預估返回行最小的表通常作爲驅動表。(連接示意圖)
嵌套循環用在一個表大一個表小的情況,哈希循環用在等值連接的情況,merge join用在不等值連接。
nested loop(嵌套循環):(關聯條件的列要有索引,驅動表的數據遠小於被驅動表)
存在着兩個循環,一個是外部循環,提取驅動表中符合條件的每條記錄。另外一個是內部循環,根據外循環中提取的每條記錄對內部表進行連接查詢相應的記錄。由於這兩個循環是嵌套進行的,故此種連接方法稱爲嵌套循環連接。
特點:
1.一個大表和一個小表(驅動表)連接,連接方式可以是等值或者是不等值
2.驅動表數據較小或者內部表已連接的列有唯一性索引或者高度可選的非唯一性索引,效率很高
3.能快速讀取結果集中第一批記錄而不必等待整個結果集完全確定下來
嵌套循環連接返回前幾行的記錄是非常快的,這是因爲使用了嵌套循環後,不需要等到全部循環結束再返回結果集,而是不斷地將查詢出來的結果集返回。在這種情況下,終端用戶將會快速地得到返回的首批記錄,且同時等待Oracle內部處理其他記錄並返回。如果查詢的驅動表的記錄數非常多,或者被驅動表的連接列上無索引或索引不是高度可選的情況,嵌套循環連接的效率是非常低的。
hash join(散列連接)(只適合出現在等值連接的情況下):
哈希連接分爲兩個階段,如下。
1、 構建階段:優化器首先選擇一張相對較小的表做爲驅動表,運用哈希函數對連接列進行計算產生一張哈希表。通常這個步驟是在內存(hash_area_size)裏面進行的,因此運算很快。
2、 探測階段:優化器對被驅動表的連接列運用同樣的哈希函數計算得到的結果與前面形成的哈希表進行探測返回符合條件的記錄。這個階段中如果被驅動表的連接列的值沒有與驅動表連接列的值相等的話,那麼這些記錄將會被丟棄而不進行探測
特點:
1.一般兩張相同大小的表連接,初始參數hash_join_enable=true
2.只能是等價連接,只能是CBO模式
3.只有一張源表需要排序,可能比merge join更快,因爲只需要對一張源表排序;
也可能比nested loop更快,因爲處理內存中的hash表比處理b-tree索引更快
4.可能會使用到臨時表空間,所以最好pag_aggregate_target設置的比較大
哈希連接比較適用於返回大數據量結果集的連接。
merge join(排序-合併):兩個互相連接的表按連接列的值先排序,排序完後形成的結果集再互相進行合併連接提取符合條件的記錄(用在>、>=、<=等情況下的連接)
特點:
1.首先對2張表的連接列進行排序後再連接
2.當缺乏數據選擇性或者有效索引時,或者2個表都比較龐大,可能比nested loop更有效
3.只能是等值連接,可能使用temp表空間
排序合併連接比較適用於返回大數據量的結果。
排序合併連接在數據表預先排序好的情況下效率是非常高的,也比較適用於非等值連接的情況,比如>、>=、<=等情況下的連接(哈希連接只適用於等值連接)
笛卡爾積
笛卡爾積連接發生在當一張表的所有行與另一張表的所有行連接的時候,因此這種連接的結果集等於兩個表的數據行數相乘。在實際應用中不使用或者避免出現這種連接。
執行計劃中每個字段的意義
operation:執行的操作類型,如Table access或sort。
options:操作的附加信息。例如,以TABLE SCAN爲例,選項可能是full或by rowid。
object_node:如果是分佈式查詢,這一列表示用於引用對象的數據庫鏈接。對於並行查詢,它的值可能對應一個臨時的結果集
object_name:對象名稱。
object_type:對象的類型(表,索引等)
cost:優化器估算出來的此操作的相對成本。
cardinality:優化器預期這一步將返回的記錄數。
bytes:預計這一步將返回的字節數。
partition_start:如果要執行分區消除(partitionelimination),這一列表明要訪問的分區範圍的起始分區。它也可能包含關鍵字key或者row location,表明要訪問的分區將在運行時確定。
partition_end:表明將要訪問的分區範圍的結束分區。
cpu_cost:估算出來的操作的cpu成本。
io_cost:估算出來的操作的io成本。
temp_space:估算出來的這一步操作所使用的臨時存儲的空間大小(如用來排序的內存或磁盤空間)。
access_predicates【訪問條件】:sql語句中,確定如何在當前步驟中提取記錄的子句。它可以包含提供給索引檢索或表連接的子句。在這塊把數據給過濾掉,一般會用到索引
filter_predicates【過濾條件】:sql語句中確定如何對記錄進行過濾的子句,如where子句在非索引列上的條件。一般不用索引,都是全表掃描,可以作爲優化重點關注的地方!
time:優化器爲這一步執行估算的時間消耗。
根據執行計劃進行優化的一般步驟
將瓶頸sql塊單獨取出,查看其執行計劃,
首先檢查其中使用了全表掃描的對象,判斷其是否合適。引發錯誤使用的情形通常是:
1) 沒有建立合適的索引列導致全表掃描
2) 非函數索引列使用了函數引發全表掃描
3) 對象統計信息過舊或未收集導致全表掃描、
4) 對於位圖索引,直方圖信息的缺失有時也會導致錯誤的全表掃描
然後檢查其中無用的表,確認無用的表可以直接去掉減少訪問步驟減小系統開銷。
接着檢查其中是否有重複訪問的表,查看是否可以減少訪問次數一般可以通過使用with或者建立中間表來優化。
優化訪問路徑,數據庫優化器也有不那麼聰明的時候,有時候它生成的訪問路徑可能並不是最優的,可嘗試使用hint來改變訪問路徑進行優化
常用hint:
/*+full( )*/表明對錶選擇全局掃描的方法.
/*+use_nl( )*/在多表關聯查詢中,指定使用nest loops方式進行多表關聯。
/*+use_hast( )*/在多表關聯查詢中,指定使用hash join方式進行多表關聯。
/*+index( )*/ 使用指定表的指定索引
/*+ append */ 以直接加載的方式將數據加載入庫
/*+leading( )*/在多表關聯查詢中,指定哪個表作爲驅動表,即告訴優化器首先要訪問哪個表上的數據。
/*+ parallel() */ 在sql中指定執行的並行度,這個值將會覆蓋自身的並行度
看Description的基數(執行結果返回的數據的行數)和自己預估的基數是否大致一直,不一致則考慮其它影響效率的情況,如統計信息比較舊等。
3. 統計信息
10g的統計信息自動收集策略
Oracle10g中統計信息默認可以自動收集,由GATHER_STATS_JOB作業收集得到,只有當數據庫對象沒有統計信息或者統計信息已經過期(Oracle 10G中是否過期的標準是數據庫對象被修改的記錄行數超過10%,該信息由Modification Monitoring來追蹤完成)時纔對該對象進行信息統計,該作業在數據庫創建或升級時由Scheduler自動創建,
這些作業可以從視圖DBA_SCHEDULER_JOBS中查到。
SELECT * FROM DBA_SCHEDULER_JOBS
通過以下包設置job的開啓與關閉
Begin
Dbms_scheduler.enable(‘GATHER_STATS_JOB’);
END;
Begin
Dbms_scheduler.disable(‘GATHER_STATS_JOB’);
END;
通過以下表查看job執行日誌
Select * from dba_scheduler_job_run_detailswhere job_name=’ GATHER_STATS_JOB’
默認情況下,Scheduler在維護窗口(maintenance window,目前227上默認啓動時間爲每晚上10:00至次日早上6:00及整個週六週日)打開時運行GATHER_STATS_JOB作業,作業GATHER_STATS_JOB則是通過調用系統內部過程DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC來完成信息統計的,該過程可根據數據庫對象統計信息需求的優先級(即數據庫對象被修改的多少)按先後順序來完成統計信息收集任務。
可以通過以下表查看窗口信息
Select * from dbs_scheduler_windows
可以通過以下包修改窗口信息(例如將SUNDAY_WINDOW的開始時間修改爲早上4點)
begin
dbms_scheduler.set_attribute
(
'SUNDAY_WINDOW','repeat_interval','freq=daily;byday=SUN;byhour=4;byminute=0; bysecond=0'
);
end;
Select * from dbs_scheduler_windows–查看是否修改成功
GATHER_STATS_JOB作業是否隨維護窗口的關閉而關閉則由屬性stop_on_window_close決定,stop_on_window_close的默認值爲TRUE,此時GATHER_STATS_JOB作業隨維護窗口的關閉而關閉。
可以通過以下匿名塊查看stop_on_window_close屬性
declare
valueboolean;
begin
dbms_scheduler.get_attribute('gather_stats_job','stop_on_window_close',value);
dbms_output.PUT_LINE('Check Result: '||casevaluewhentruethen'True'else'False'end);
end;
可以通過以下包修改屬性
execdbms_scheduler.set_attribute(' gather_stats_job
','stop_on_window_close',TRUE);
非默認情況時,Oracle10g可通過設置初始化參數 STATISTIC_LEVEL(STATISTICS_LEVEL = {ALL | TYPICAL | BASIC}),來控制是否啓用統計信息自動收集功能
當其爲默認值TYPICAL時,系統將自動收集所有主要的有關自身管理的信息以使系統提供最優性能,該值適合於絕大多數情況;
當取值ALL時,相對TYPICAL值系統增加timedOS statistics和plan execution statistics兩項信息統計;
當取值 BASIC時:有關係統特性和功能的許多信息統計功能都將被關閉
查看當前值
Show parameter statistics_level
修改語法
ALTER SESSION SET statistics_level=all–修改當前會話
ALTER SYSTEM SET statistics_level=all–修改整個系統
10g的統計信息手動收集方法
除系統自動收集統計信息外,還可以通過手動調用包來收集統計信息
常用的包如下
dbms_stats.gather_table_stats();--收集指定表的統計信息
常用參數
Tabname–表名稱
Partname—分區名稱
estimate_percent—統計的樣品比例,默認oracle自動選擇
method_opt–統計方式,默認FOR ALL COLUMNS SIZE AUTO.
degree –並行度
cascade—是否級聯收集索引信息,默認是不收集的
dbms_stats.gather_index_stats();--收集指定索引的統計信息
另外還有一個收集統計信息的命令analyze
analyzetable t1 computestatistics–收集表統計信息
以上包或命令常用在對單個對象收集信息,當對象較多或者需要對某個用戶下的某類對象進行收集信息就要用到下面的包
dbms_stats.gather_schema_stats();--收集指定用戶的統計信息
常用參數
Ownname–用戶名稱
estimate_percent—抽樣比例,默認oracle自動選擇
method_opt--統計方式,默認FOR ALL COLUMNSSIZE AUTO.
Degree–並行度
Granularity—收集統計信息的級別默認auto
-- 'AUTO' – 由過程自動決定收集的級別
-- 'GLOBAL AND PARTITION' – 收集全局以及分區信息
-- 'SUBPARTITION' – 收集子分區信息
-- 'PARTITION' – 收集分區信息
-- 'GLOBAL' – 收集全局信息
-- 'ALL' - 收集全局、分區、子分區信息
Cascade—是否收集索引信息,默認由oracle決定
options – 指定收集的對象
-- 'GATHER' – 收集用戶的所有對象信息
-- 'GATHER AUTO' – 由oracle決定收集哪些對象
-- 'GATHER STALE' – 收集視圖user_tab_modifications中的對象信息與系統自動收集的策略一致
-- 'GATHER EMPTY' – 收集當前統計信息爲空的對象信息
在自動收集策略中也提到了一個收集統計信息的包
DBMS_STATS.GATHER_DATABASE_STATS_JOB_PRO()
這個包的統計原理是統計數據庫中統計信息過久或者缺失或者對象在統計時間段內數據量變化大於10%(可以累積)
監控對象修改數據量的視圖
select*fromuser_tab_modifications
對象中的數據發生變化後並不會立即進入到user_tab_modifications,可以利用以下包手工刷新
begin
Dbms_Stats.flush_database_monitoring_info();
end;
手動調用收集統計信息
begin
dbms_stats.gather_database_stats_job_proc();--通過實驗發現對於分區表,只收集分區表的信息,全表的不收集(需要通過修改時間觸發自動收集job纔會收集全表的信息)
end;
當分區表修改數據量達到10%但沒到全表的10%,則只收集分區表的信息,不收集全表的信息,當達到全表的10%則會收集全表的信息。
修改量可以累積當累積到10%後也會觸發收集。
dbms_stats.gather_schema_stats()中的參數‘GATHER STALE'也是利用通過監控這個視圖中的數據來決定收集的對象。
在收集統計信息時可能出現原來的優化方法在收集統計信息之前一直工作良好,但是在此之後,由於新收集的統計信息產生了不良計劃,導致查詢突然出錯或效率降低。爲避免這種情況,統計信息的收集作業在收集新信息之前保存當前的統計信息。如果出現問題,則可以返回到原有的統計信息,或者通過歷史統計檢查二者之間的不同之處,以解決問題。
查詢系統保存統計信息時長
select DBMS_STATS.GET_STATS_HISTORY_RETENTIONfrom dual;
查詢最早可用的統計信息時間
selectDBMS_STATS.GET_STATS_HISTORY_AVAILABILITY from dual;
還原統計用戶統計信息
begin
dbms_stats.restore_schema_stats
(user,'09-6月 -16 10.26.32.927000000 下午 +08:00');
end;
在利用系統自動保存的信息進行還原的同時也可以利用命令手動導入導出用戶統計信息
--創建收集統計信息的表stattab
begin
dbms_stats.create_stat_table('scott','stattab','users');
end;
--將用戶統計信息導出到統計信息表stattab
begin
dbms_stats.export_schema_stats
(
ownname=>'scott',stattab=>'stattab',statown=>'scott'
);
end;
--收集用戶的統計信息
begin
dbms_stats.gather_schema_stats
(
'scott',method_opt=>'forall columns',degree=>4
);
end;
--將用戶統計信息還原
begin
dbms_stats.import_schema_stats
(ownname=>'scott',STATTAB=>'stattab',statown=>'scott');
end;
SQL動態採樣
動態取樣是爲謂詞和表/索引統計收集更加精確的信息從而提高服務器性能,信息越精確產生的性能更好。
可以使用動態取樣的情況:
1.) 在收集的統計不能使用或會導致嚴重的估計錯誤時估計單表的謂詞選擇性;
2.) 估計沒有統計的表/索引的統計;
3.) 估計統計過期的表和索引的統計;
動態取樣特徵由參數OPTIMIZER_DYNAMIC_SAMPLING控制,默認級別爲2。
動態採樣是在解析的時候對錶進行採樣收集統計信息,但不會寫入user_tables
動態採樣可以通過hint開啓無論OPTIMIZER_DYNAMIC_SAMPLING目前的值是多少
取樣級別範圍從1..10
1級:滿足以下條件則採樣所有沒被分析的表:
(1)查詢中至少有一個未分析表;
(2)這個未分析表被關聯另外一個表或者出現在子查詢或非merge視圖中;
(3)這個未分析表有索引;
(4)這個未分析表有多餘動態採樣默認的數據塊數(默認是32塊)。
2級:對所有未分析表進行動態採樣。採樣數據塊數量是默認數量的2倍。
3級:在2級基礎上加上那些使用了猜想選擇消除表,採樣數據塊數量等於默認數量。對於未分析表,採樣數量2倍於默認數量。
4級:在3級基礎上加上那些有單表謂詞關聯2個或多個列,採樣數據塊數量等於默認數量。對於未分析表,採樣數量2倍於默認數量。
5,6,7,8,9級在4級基礎上分別使用2,4,8,32,128倍於默認動態採樣數據塊數量。
10級:在9級基礎上對錶中所有數據塊進行採樣。
新建一個t2表,不對其收集統計信息,查詢其中一個數據
SQL> select /*+dynamic_sampling(t 0)*/ * from t2 where id=30101;
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 127 | 3302 | 44 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T2 | 127 | 3302 | 44 (0)| 00:00:01 |
--------------------------------------------------------------------------
SQL> select/*+dynamic_sampling(t 3)*/ * from t2 t where id=30101;
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 45 (3)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T2 | 1 | 26 | 45 (3)| 00:00:01 |
--------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement (level=0)–這個值需單獨設置
通過在sql使用hint開啓動態採樣級別能夠發現開啓後訪問的行數明顯下降
4. 分區表
1.創建的分區表信息清單(數據採樣時間201603)
表英文名 | 表中文名 | 總的數據量大小 | 總的數據量條數 | 每月的數據量大小 | 每月的數據量條數 |
f_ac_inter | 內部戶事實表 | 9.14G | 35099569 | 0.23G | 896098 |
F_AC_JYMX_ALL | 交易明細中間表 | 2.7G | 11630307 | 0.06G | 272207 |
f_ac_jymx_d | 交易明細日表 | 3.6G | 11563443 | 0.08G | 240419 |
F_ac_ledger | 日總賬 | 10.1G | 27214046 | 0.24G | 523420 |
T05_LOAN_PROVISION |
| 1.4G | 11094224 | 0.07G | 592680 |
T09_SUB_ACCU_BALANCE_H | 科目賬戶餘額表 | 2.28G | 18127253 | 0.1G
| 904879 |
T05_DEPOSIT_PROVISION |
| 1.7G | 14865031 | 0.08g | 801128 |
t03_inter_acun_dtl | 內部戶賬務明細 | 6.09G | 33099597 | 0.15G | 896098 |
t05_core_acun_dtl | 核心賬務明細 | 4.78G | 24347280 | 0.14G | 686731 |
T05_CORE_TRAD_DTL | 核心交易明細 | 23G | 27190137 | 0.6G | 682783 |
2.創建分區表前的分析
從表格統計信息,我們看到這些表都屬於大表;其次在分析後臺腳本後發現,這些表都是前臺和後臺腳本中用的比較頻繁的表,同時,這些表在腳本查詢中大都使用的是當月數據,因此,
我們把這些表改爲以work_dt爲分區列的月分區表
3.分區表類型
在本次的項目優化中,我們所使用的分區表類型是範圍分區(range).
由於我們本次的生產數據庫版本是ORACLE10g,因此,我們列舉出了ORACLE10g的幾種分區類型如下:
分區表類型 | 簡述 |
RANGE(範圍)分區 | 例如查詢某個月的數據 |
LIST(列表)分區 | 例如查詢某個地區的數據 |
HASH(哈希)分區 | 數據分配到每個分區的量是均衡的 |
組合分區 | 分爲(range-list,range-hash) |
4.分區表優點與缺點
優點 | 缺點 |
1. 分區消除:減少訪問路徑 2. 記錄清理:刪除記錄高效(truncate) 3. 分區轉移(exchange partition 分區名 with table tabname);將某分區數據與表互換 | 1.分區表的缺點:分區過多,會加大Oracle對段的管理。同時內部會產生大量的遞歸調用。一般數據記錄在100萬以下的不 建議創建分區表。
|
5.分區表的常見使用方式
a) truncate分區數據
alter table range_part_tab truncatepartition p1 ;
b) drop分區
alter table range_part_tab droppartition p_max;
c) 添加分區
alter table range_part_tab add partitionp2013_02 values less than(to_date('2013-03-01','yyyy-m
m-dd'));
d) 常用視圖
user_tab_partitions (查詢一個分區表的各個分區名)
6.分區表的維護
分區的增加:在10G中沒有自動增加分區的功能因此本輪優化中採取了手動維護的方式,通過腳本在每月1號增加下個月的分區。
分區統計信息收集:分區表的統計信息主要是通過數據庫自動收集統計信息的策略實現的,但在日常執行過程中出現了每月2號(sysdate時間)引用了部分分區表的sql執行緩慢的情況,是因爲新增的分區的統計信息是空的,因此在分區插入數據後導致了優化器沒有準確的統計信息可供使用出現執行計劃變差。目前採取的補救方案是每月3號早上在自動收集統計信息完成後將分區的統計信息備份一份,在下月2號之前恢復給新建分區上。使優化器有統計信息可以使用避免了執行計劃變差。
分區數據接入:採取分區表後可以對分區進行截斷操作即減少了執行時間也能避免之前delete而產生的高水位線。因此在對按月接入數據的分區表採取截斷操作後再接入數據。
5. 索引:一種數據庫結構,用來快速查找數據,一般包括根節點,葉節點。葉子節點存儲索引條目,一般條目裏包含索引鍵值(單列索引是一個值多列索引是多個值組合)與rowid
B樹索引
目前數據庫中最常用的索引,構造類似於二叉樹,能根據鍵值提供一行或一個行集的快速訪問,其中的’B’代表平衡, 通常使用在頻繁使用查詢謂詞的列上,一般這類列的選擇度都較高。
使用場景
1、當我們希望從表中只返回少量的數據(佔比很小,這個比例通常經驗值是5%,不過根據表的不同也有不用,一個瘦表(通常只有幾列)可能在20%-30%,一個胖表(列很多或列很寬)可能在2%-3%)時會使用索引。如下例:
T1表插入從1到10w的數字只有一列,t3_2利用下例中的t3建表,建表sql
createtable t3_2asselectid,name,name||name||name||name c3,name||name||name||name||name c4 fromt3
分別在id列建立索引,收集統計信息
以上兩個查詢中的數值分別是全表掃描與索引範圍查詢的臨界值
2、當我們想要查詢大量數據,但是隻要返回索引的列或者只通過索引列就能得到結果的話,索引也會起到作用
3、數據在磁盤上的物理組織也會對索引的使用有影響,如以下的例子,我們創建兩個實驗表t2/t3,向t2中順序的插入10w條數據,同時生成一組隨機數據。將t2按照隨機數排序插入到t3中,目的是打亂數據的物理存儲位置。
begin
for i in1..100000loop
insertinto t2 values(i,rpad(dbms_random.random,75,'*'));
endloop;
commit;
end;
createtable t3 asselect*from t2 orderbyname
createindex idx_1on t2(id)
createindex idx_2 on t3(id)
begin
dbms_stats.gather_table_stats
('scott','t2');
begin
dbms_stats.gather_table_stats
('scott','t3');
end;
end;
在對錶進行收集統計信息後,分別對t2/t3查詢相同範圍的數據
select*from t2 whereidbetween87and1000
select*from t3 whereidbetween87and1000
以上三個查詢的結果是相同的,但是相同的查詢數據庫的開銷與io上升的差異十分明顯。
原因是當向一個表中填充數據時如果按照行主鍵或者建立索引的列順序填充,序號相鄰的行存儲位置一般也會相鄰,當你發出一個範圍查詢的時候你想要的行通常也在同樣的塊上,即使你要查找大量的行,通過索引範圍掃描的讀取的塊裏也許就包含了你想要的行。如下圖,相同數量的行數在未打亂順序的t2中分佈在11個數據塊,在順序被打亂的t3中分佈在631個塊。
如果行被分散的存儲在不同位置上,此時強制使用索引範圍掃描就會是個災難,使用全表掃描反而更好。
總結
1、通過索引訪問表中的數據佔比越少越有效
2、如果能使用索引列回答問題(只用到索引列不用訪問表)那麼返回數據佔比很大索引也是有效的
3、數據的物理組織有時未按照索引列或主鍵列有序的填充表,會影響索引的使用
4、空值會影響索引的使用,在有空值的列上通過與虛擬列建立組合索引,可以使優化器選擇索引。而且索引的大小並沒有明顯變化
createtable t5 asselect u.OBJECT_NAME,u.DATA_OBJECT_IDfrom user_objects u
createindex t5_1on t5(DATA_OBJECT_ID)
createtable t6 asselect*from t5
createindex t6_1on t6(DATA_OBJECT_ID,0)
begin
dbms_stats.gather_table_stats
('scott','t5');
dbms_stats.gather_table_stats
('scott','t6');
end;
5、B樹索引經過大量的插入刪除操作以後一個是容易使樹不平衡,再一個是刪除後空間不回收。所以定期重建索引非常有必要。
位圖索引
在B樹索引中,索引鍵值與行之間存在一種一對一的關係,一個索引鍵值引向一行,而在位圖索引中,一個索引鍵值則對應多行,位圖索引通常適用於高度重複(相對於很多的行數,列值可能只有幾個,列值/行數越接近0則越適合使用位圖索引)而且經常只讀的列,通常查詢這種列返回的數據佔比很大,因此也不適合使用B樹索引。對比來看,B樹索引通常是選擇性的,位圖索引位通常不是選擇性的。位圖索引的鍵值使用0,1存儲,相較B樹索引節省很大的空間另外位圖索引可以存儲NULL值。
使用場景
1、一個查詢條件包含多個列,並且要創建索引的列只有幾個不同的值及大量的聚合統計查詢where條件中使用and/or/in
如截圖中的查詢例子。如果建立B樹索引,爲了高效的滿足查詢要求,就要建立2或者更多的索引組合來實現,這將會佔用大量的數據庫空間如果後期條件有調整維護起來也比較麻煩。
如果建立位圖索引,oracle會對3個索引的位圖使用and、or或not得到合併後的位圖,如果有必要可以將位圖中的‘1’轉換成rowid來訪問數據,如果是計數則直接統計1的個數。
如下例子:
createtable t4
(
a notnull,
b notnull,
c notnull
)
as
select
decode(ceil(dbms_random.value(1,2)),1,'M',2,'F'),
ceil(dbms_random.value(1,50)),
decode
(
ceil(dbms_random.value(1,5)),
1,'18and under',
2,'19-25',
3,'26-30',
4,'31-40',
5,'41and over'
)
fromdual connectbylevel<=100000;
createbitmapindex t4_1on t4(a);
createbitmapindex t4_2on t4(b);
createbitmapindex t4_3on t4(c);
begin
dbms_stats.gather_table_stats
('scott','t4');
end;
總結
1. 位圖索引使用於低基數的列(比如說性別列,數據倉庫中的維表的主鍵),相對於B樹索引,它的count,and,or操作更有效
2. 位圖索引存放的是0,1的比特位,相對於B樹索引,佔字節數特別少
使用位圖索引要特別注意
1. 列的基數比較多,不適合位圖索引,因爲它會佔用更多的存儲空間
2.索引列DML頻繁的列,不適合位圖索引,容易造成死鎖,原因是一個位圖索引鍵值指向多行,如果一個會話修改了一行數據,大多數情況下這個鍵值所對應的所有行都會被鎖定。大大影響到系統併發性。數據倉庫項目中對於位圖索引的維護一般建議先刪掉索引加載完完數據後再建立索引
3.關於列偏態或稱列傾斜、傾斜列對使用索引的影響,這種列的特點是數據大多集中在某幾個值。這種情況下一般會影響索引的使用,通常情況下需要收集表的直方圖信息來使優化器決定是否使用索引。
如下例子:
createtable t8
(a notnull,b notnull,c notnull)
as
select
'M',
ceil(dbms_random.value(1,50)),
decode
(
ceil(dbms_random.value(1,5)),
1,'18and under',
2,'19-25',
3,'26-30',
4,'31-40',
5,'41and over'
)
fromdual connectbylevel<=100000;
insertinto t8
select
'F',
ceil(dbms_random.value(1,50)),
decode
(
ceil(dbms_random.value(1,5)),
1,'18and under',
2,'19-25',
3,'26-30',
4,'31-40',
5,'41and over'
)
from dual connectbylevel<=10;
createbitmapindex t8_1on t8(a);
createbitmapindex t8_2on t8(b);
createbitmapindex t8_3 on t8(c);
begin
dbms_stats.gather_table_stats
('scott','t8');
end;
createtable t7
(
a notnull,
b notnull,
c notnull
)
as
select
'M',
ceil(dbms_random.value(1,50)),
decode
(
ceil(dbms_random.value(1,5)),
1,'18and under',
2,'19-25',
3,'26-30',
4,'31-40',
5,'41and over'
)
fromdual connectbylevel<=100000;
insertinto t7
select
'F',
ceil(dbms_random.value(1,50)),
decode
(
ceil(dbms_random.value(1,5)),
1,'18and under',
2,'19-25',
3,'26-30',
4,'31-40',
5,'41and over'
)
from dual connectbylevel<=10;
createbitmapindex t7_1on t7(a);
createbitmapindex t7_2on t7(b);
createbitmapindex t7_3 on t7(c);
begin
dbms_stats.gather_table_stats
('scott','t7',METHOD_OPT=>’forall columns size skewonly’);
end;
建立t8/t7,其中M對應10w條數據,f對應10條數據,這個一個典型的偏態列,區別是對t7收集了其直方圖信息。
執行相同的查詢,在查詢t8表時無論查‘M’還是‘F’兩個執行計劃都使用了全表掃描,而且優化器認爲兩個值的基數一致都是5w左右。
在查詢t7表時,可以發現執行計劃明顯不同,而且優化器識別出了兩個值的基數不同,接近真實值。
分區表索引
在分區表中經常使用的兩種索引,本地索引和全局索引。一般使用LOCAL索引較爲方便,而且維護代價較低,並且LOCAL索引是在分區的基礎上去創建索引,類似於在一個子表內部去創建索引,這樣開銷主要是區分分區上,很規範的管理起來;而相對的GLOBAL索引是全局類型的索引,根據實際情況可以調整分區的類別,而並非按照分區結構一一定義,相對維護代價較高一些,在應用過程中依據實際情況而定,來提高整體的運行性能。
根據經驗總結了本地索引與全局索引使用技巧
1、如果使用全局索引,當對某一個分區進行ddl操作時,該索引就無效了,必須重建,這一點比較麻煩。(DDl操作對全局索引和本地索引的影響詳見下表的總結)可以在語句後增加UPDATEindexes在截斷分區的同時維護全局索引避免失效
2、如果索引字段是分區鍵(主要是range),那麼就用local的。
3、如果索引字段是id、電話號碼等類型的,那麼就用global的。
4、如果分區間的數據是相互獨立的,即不會被同時訪問,使用local index 更好些。相反如果數據跨越多個分區,可能local index會更差些。
5、當分區中出現許多事務並且要保證所有分區中的數據記錄的唯一性時採用全局索引。
各種操作對不同類型索引的影響
表級分區操作 | 本地分區索引 | 全局分區索引 |
增加分區 | 索引不受影響 | 索引不受影響 |
拆分分區 | 受拆分操作影響的分區上的索引被標記爲UNUSABLE | 索引的所有分區都被標記爲UNUSABLE |
移動分區 | 被移動的分區上的索引被標記爲UNUSABLE | 索引的所有分區都被標記爲UNUSABLE |
交換分區 | 被交換的分區上的索引被標記爲UNUSABLE | 索引的所有分區都被標記爲UNUSABLE |
合併分區 | 受合併操作影響的分區上的索引被標記爲UNUSABLE | 索引的所有分區都被標記爲UNUSABLE |
截斷分區 | 索引不受影響 | 索引的所有分區都被標記爲UNUSABLE |
刪除分區 | 本地索引分區被刪除,其餘索引分區不受影響 | 索引的所有分區都被標記爲UNUSABLE |
令數據只讀 | 通過表空間隔離可以令分區級別索引數據只讀 | 理論上可以令分區級別索引數據只讀。 實際上無法實現,除非整個表是靜態的 |
組合索引與單列索引
整體來講,索引是爲了更快的查找到數據而產生的,一般建立在選擇度較高的列上(位圖索引不在這個討論範疇)。組合索引一般是通過多列組合來達到提高選擇度的,代價是索引比較佔用空間,如下例:
我們複製t3表爲t3_1,同時在id與name上建立組合索引(IDX_T31),對比兩個索引大小
由於name列的加入,t3_1索引的大小是t3的5倍多,接下里查詢相同的值來對比執行計劃中的消耗
可以看到雖然結果相同,但是使用組合索引的字節與cpu消耗均高出不少。(某些特定情況,比如說要取出的列都是索引列,那麼就會減少一步從表中讀數)
綜合以上實驗可以知道,雖然執行計劃都選擇了走索引但是兩個索引所佔用的物理空間與被引用時帶來的開銷有很大不同,當單列選擇度較高的時候儘量選擇單列索引,只有當單列索引的選擇度不夠高時才考慮使用組合索引來提高選擇度。
函數索引
函數索引與B*樹索引的結構存在很大相似性。區別就在於形成樹結構的葉子節點上,保存的不是索引列的取值,而是經過特定的函數處理過的索引列值。這樣的結構,進行搜索的時候,就可以直接使用到函數索引的葉子節點,獲取到對應的rowid集合。
B樹索引通常用在精確匹配或者小範圍的查詢,但是當進行模糊匹配時就會常常引起B樹索引的失效,這時就可以用到函數索引。
函數索引的使用要注意以下幾點
首先,函數索引的綜合消耗要大於普通的B*樹索引。相對於傳統索引,函數索引要保證創造的函數列數據一致性和多次進行函數計算。這樣的消耗要遠大於普通B*樹索引;
其次,函數索引的適應範圍較小。函數索引生效的最大要素就是函數的使用和定義是100%相同。
最後特別注意在10gR2版本中,發生過刪除函數索引導致引用這個表的存儲過程編譯失效的情況,臨時解決方案是在刪除這個索引後把所有失效的腳本重新編譯一次。所以更要慎重選擇使用函數索引。
函數索引通常是一種事後補救措施。一個良好設計的應用,一個劃分合理的數據庫邏輯結構,應該是可以避免函數操作數據列的SQL大量出現的。只有在系統上線之後,問題暴露出來,但沒精力進行修改時或修改代價太大,纔開始使用函數索引,保證系統功能能夠實現。
6. 其他優化方案
除以上總結的優化方法外在本輪優化中還嘗試使用了其他方法,主要如下:
l 定位腳本中的sql 瓶頸:使用pl/sql developer測試窗口中的profiler來定位是哪一段sql耗時較久
l 去掉orderby,排序是一項開銷很大的操作,如果非必須可以去掉(需要考慮B樹索引中的簇族因子,如果列的排序過於無序也會導致索引的失效參看B樹索引部分,這是一個需要平衡的選擇)
l 將leftjoin 改寫成子查詢,根據子查詢的特性對於返回到外層查詢的記錄來說,子查詢會每次執行一次。因此,必須保證任何可能的時候子查詢都要使用索引,如果父查詢只返回較少的記錄,那麼再次執行子查詢的開銷不會非常大。因此在連接左側的表如果在執行計劃中使用了全表掃描並且沒有參與where的條件可以考慮轉換成關聯子查詢。
l 使用語句級並行,用資源換時間多數使用在全表掃描情況下。
l 增加過濾條件,可能會更改業務邏輯需要慎重選擇。
l 一段sql中對一張大表進行了全表查詢部分列,這種情況,需要使用全量抽取改增量抽取的方式來優化