Oracle資料庫訪問效能優化

smooth-z發表於2018-02-20

       所有資料庫包括Oracle的sql優化都是針對程式設計師的,而不是針對dba的,第一,儘量防止模糊,明確指出,即用列名代替*,第二,在where語句上下工夫。第三多表查詢和子查詢,第四儘量使用繫結。


       根據計算機硬體的基本效能指標及其在資料庫中主要操作內容,可以整理出如下圖所示的效能基本優化法則:


       這個優化法則歸納為5個層次:
       1、減少資料訪問(減少磁碟訪問)
       2、返回更少資料(減少網路傳輸或磁碟訪問)
       3、減少互動次數(減少網路傳輸)
       4、減少伺服器CPU開銷(減少CPU及記憶體開銷)
       5、利用更多資源(增加資源)
       由於每一層優化法則都是解決其對應硬體的效能問題,所以帶來的效能提升比例也不一樣。傳統資料庫系統設計是也是儘可能對低速裝置提供優化方法,因此針對低速裝置問題的可優化手段也更多,優化成本也更低。我們任何一個SQL的效能優化都應該按這個規則由上到下來診斷問題並提出解決方案,而不應該首先想到的是增加資源解決問題。

       以下是每個優化法則層級對應優化效果及成本經驗參考:

優化法則

效能提升效果

優化成本

減少資料訪問

1~1000

返回更少資料

1~100

減少互動次數

1~20

減少伺服器CPU開銷

1~5

利用更多資源

@~10

1 減少資料訪問

1.1 建立並使用正確的索引

       資料庫索引的原理非常簡單,但在複雜的表中真正能正確使用索引的人很少,即使是專業的DBA也不一定能完全做到最優。
       索引會大大增加表記錄的DML(INSERT,UPDATE,DELETE)開銷,正確的索引可以讓效能提升100,1000倍以上,不合理的索引也可能會讓效能下降100倍,因此在一個表中建立什麼樣的索引需要平衡各種業務需求。
一、SQL什麼條件會使用索引?
       當欄位上建有索引時,通常以下情況會使用索引:
       INDEX_COLUMN = ?
       INDEX_COLUMN > ?
       INDEX_COLUMN >= ?
       INDEX_COLUMN < ?
       INDEX_COLUMN <= ?
       INDEX_COLUMN between ? and ?
       INDEX_COLUMN in (?,?,...,?)
       INDEX_COLUMN like ?||'%'(後導模糊查詢)
       T1. INDEX_COLUMN=T2. COLUMN1(兩個表通過索引欄位關聯)

二、SQL什麼條件不會使用索引?

查詢條件

不能使用索引原因

INDEX_COLUMN <> ?

INDEX_COLUMN not in (?,?,...,?)

不等於操作不能使用索引

function(INDEX_COLUMN) = ?

INDEX_COLUMN + 1 = ?

INDEX_COLUMN || 'a' = ?

經過普通運算或函式運算後的索引欄位不能使用索引

INDEX_COLUMN like '%'||?

INDEX_COLUMN like '%'||?||'%'

含前導模糊查詢的Like語法不能使用索引

INDEX_COLUMN is null

B-TREE索引裡不儲存欄位為NULL值記錄,因此IS NULL不能使用索引

NUMBER_INDEX_COLUMN='12345'

CHAR_INDEX_COLUMN=12345

Oracle在做數值比較時需要將兩邊的資料轉換成同一種資料型別,如果兩邊資料型別不同時會對欄位

值隱式轉換,相當於加了一層函式處理,所以不能使用索引。

a.INDEX_COLUMN=a.COLUMN_1

給索引查詢的值應是已知資料,不能是未知欄位值。

注:經過函式運算欄位的欄位要使用可以使用函式索引,這種需求建議與DBA溝通。

有時候我們會使用多個欄位的組合索引,如果查詢條件中第一個欄位不能使用索引,那整個查詢也不能使用索引。

如:我們company表建了一個id+name的組合索引,以下SQL是不能使用索引的

Select * from company where name=?

Oracle9i後引入了一種index skip scan的索引方式來解決類似的問題,但是通過index skip scan提高效能的條件比較特殊,使用不好反而效能會更差。

三、我們一般在什麼欄位上建索引?
       這是一個非常複雜的話題,需要對業務及資料充分分析後再能得出結果。主鍵及外來鍵通常都要有索引,其它需要建索引的欄位應滿足以下條件:
       1、欄位出現在查詢條件中,並且查詢條件可以使用索引;
       2、語句執行頻率高,一天會有幾千次以上(併發測試必須有索引);
       3、通過欄位條件可篩選的記錄集很小,那資料篩選比例是多少才適合?
       這個沒有固定值,需要根據表資料量來評估,以下是經驗公式,可用於快速評估:
       小表(記錄數小於10000行的表):篩選比例<10%;
       大表:(篩選返回記錄數)<(表總記錄數*單條記錄長度)/10000/16
       單條記錄長度≈欄位平均內容長度之和+欄位數*2

       以下是一些欄位是否需要建B-TREE索引的經驗分類:

 

欄位型別

常見欄位名

需要建索引的

欄位

主鍵

ID,PK

外來鍵

PRODUCT_ID,COMPANY_ID,MEMBER_ID,ORDER_ID,TRADE_ID,PAY_ID

有對像或身份標識意義欄位

HASH_CODE,USERNAME,IDCARD_NO,EMAIL,TEL_NO,IM_NO

索引慎用欄位,

需要進行資料

分佈及使用場

景詳細評估

日期

GMT_CREATE,GMT_MODIFIED

年月

YEAR,MONTH

狀態標誌

PRODUCT_STATUS,ORDER_STATUS,IS_DELETE,VIP_FLAG

型別

ORDER_TYPE,IMAGE_TYPE,GENDER,CURRENCY_TYPE

區域

COUNTRY,PROVINCE,CITY

操作人員

CREATOR,AUDITOR

數值

LEVEL,AMOUNT,SCORE

長字元

ADDRESS,COMPANY_NAME,SUMMARY,SUBJECT

不適合建索引

的欄位

描述備註

DESCRIPTION,REMARK,MEMO,DETAIL

大欄位

FILE_CONTENT,EMAIL_CONTEN

四、如何知道SQL是否使用了正確的索引?
       簡單SQL可以根據索引使用語法規則判斷,複雜的SQL不好辦,判斷SQL的響應時間是一種策略,但是這會受到資料量、主機負載及快取等因素的影響,有時資料全在快取裡,可能全表訪問的時間比索引訪問時間還少。要準確知道索引是否正確使用,需要到資料庫中檢視SQL真實的執行計劃,這個話題比較複雜,詳見SQL執行計劃專題介紹。

以下是執行計劃獲取方式:

select SQL_TEXT,SQL_ID,OPTIMIZER_MODE,parsing_schema_name,LAST_ACTIVE_TIME from V$SQLAREA;

select*fromtable(dbms_xplan.display_cursor('gsyvkdr40w01r'));----gsyvkdr40w01r為SQL_ID

五、索引對DML(INSERT,UPDATE,DELETE)附加的開銷有多少?
       這個沒有固定的比例,與每個表記錄的大小及索引欄位大小密切相關,以下是一個普通表測試資料,僅供參考:
       索引對於Insert效能降低56%
       索引對於Update效能降低47%
       索引對於Delete效能降低29%
       因此對於寫IO壓力比較大的系統,表的索引需要仔細評估必要性,另外索引也會佔用一定的儲存空間。

1.2 只通過索引訪問資料

      有些時候,我們只是訪問表中的幾個欄位,並且欄位內容較少,我們可以為這幾個欄位單獨建立一個組合索引,這樣就可以直接只通過訪問索引就能得到資料,一般索引佔用的磁碟空間比表小很多,所以這種方式可以大大減少磁碟IO開銷。
      如:select id,name from company where type='2';
      如果這個SQL經常使用,我們可以在type,id,name上建立組合索引
      create index my_comb_index on company(type,id,name);
      有了這個組合索引後,SQL就可以直接通過my_comb_index索引返回資料,不需要訪問company表。
      還是拿字典舉例:有一個需求,需要查詢一本漢語字典中所有漢字的個數,如果我們的字典沒有目錄索引,那我們只能從字典內容裡一個一個字計數,最後返回結果。如果我們有一個拼音目錄,那就可以只訪問拼音目錄的漢字進行計數。如果一本字典有1000頁,拼音目錄有20頁,那我們的資料訪問成本相當於全表訪問的50分之一。

      切記,效能優化是無止境的,當效能可以滿足需求時即可,不要過度優化。在實際資料庫中我們不可能把每個SQL請求的欄位都建在索引裡,所以這種只通過索引訪問資料的方法一般只用於核心應用,也就是那種對核心表訪問量最高且查詢欄位資料量很少的查詢。

1.3 優化SQL執行計劃

      SQL執行計劃是關係型資料庫最核心的技術之一,它表示SQL執行時的資料訪問演算法。由於業務需求越來越複雜,表資料量也越來越大,程式設計師越來越懶惰,SQL也需要支援非常複雜的業務邏輯,但SQL的效能還需要提高,因此,優秀的關係型資料庫除了需要支援複雜的SQL語法及更多函式外,還需要有一套優秀的演算法庫來提高SQL效能。
      目前ORACLE有SQL執行計劃的演算法約300種,而且一直在增加,所以SQL執行計劃是一個非常複雜的課題,一個普通DBA能掌握50種就很不錯了,就算是資深DBA也不可能把每個執行計劃的演算法描述清楚。雖然有這麼多種演算法,但並不表示我們無法優化執行計劃,因為我們常用的SQL執行計劃演算法也就十幾個,如果一個程式設計師能把這十幾個演算法搞清楚,那就掌握了80%的SQL執行計劃調優知識。

      推薦使用的SQL執行計劃優化工具:Dell SQL Optimizer for Oracle

2 返回更少的資料

2.1 資料分頁處理

一般資料分頁方式有:
1、客戶端(應用程式或瀏覽器)分頁
      將資料從應用伺服器全部下載到本地應用程式或瀏覽器,在應用程式或瀏覽器內部通過原生程式碼進行分頁處理
      優點:編碼簡單,減少客戶端與應用伺服器網路互動次數
      缺點:首次互動時間長,佔用客戶端記憶體
      適應場景:客戶端與應用伺服器網路延時較大,但要求後續操作流暢,如手機GPRS,超遠端訪問(跨國)等等。
2、應用伺服器分頁
     將資料從資料庫伺服器全部下載到應用伺服器,在應用伺服器內部再進行資料篩選。以下是一個應用伺服器端Java程式分頁的示例:
     List list=executeQuery(“select * from employee order by id”);
     Int count= list.size();
     List subList= list.subList(10, 20);
      優點:編碼簡單,只需要一次SQL互動,總資料與分頁資料差不多時效能較好。
      缺點:總資料量較多時效能較差。
      適應場景:資料庫系統不支援分頁處理,資料量較小並且可控。
3、資料庫SQL分頁
      採用資料庫SQL分頁需要兩次SQL完成
      一個SQL計算總數量
      一個SQL返回分頁後的資料
      優點:效能好
      缺點:編碼複雜,各種資料庫語法不同,需要兩次SQL互動。
      oracle資料庫一般採用rownum來進行分頁,常用分頁語法有如下兩種:

(1)直接通過rownum分頁:

select*from(

         select a.*,rownum rn from

                   (select*from product a where company_id=? orderby status) a

         whererownum<=20)

where rn>10;

      資料訪問開銷=索引IO+索引全部記錄結果對應的表資料IO
(2)採用rowid分頁語法

      優化原理是通過純索引找出分頁記錄的ROWID,再通過ROWID回表返回資料,要求內層查詢和排序欄位全在索引裡。

createindex myindex on product(company_id,status);

select b.*from(

         select*from(

                   select a.*,rownum rn from

                            (selectrowid rid,status from product a where company_id=? orderby status) a

                   whererownum<=20)

         where rn>10) a, product b

wherea.rid=b.rowid;

       資料訪問開銷=索引IO+索引分頁結果對應的表資料IO
       例項:
       一個公司產品有1000條記錄,要分頁取其中20個產品,假設訪問公司索引需要50個IO,2條記錄需要1個表資料IO。

那麼按第一種ROWNUM分頁寫法,需要550(50+1000/2)個IO,按第二種ROWID分頁寫法,只需要60個IO(50+20/2);

2.2 只返回需要的欄位

       通過去除不必要的返回欄位可以提高效能,例:
       調整前:select * from product where company_id=?;
       調整後:select id,name from product where company_id=?;
       優點:
       1、減少資料在網路上傳輸開銷
       2、減少伺服器資料處理開銷
       3、減少客戶端記憶體佔用
       4、欄位變更時提前發現問題,減少程式BUG
       5、如果訪問的所有欄位剛好在一個索引裡面,則可以使用純索引訪問提高效能。
       缺點:增加編碼工作量
       由於會增加一些編碼工作量,所以一般需求通過開發規範來要求程式設計師這麼做,否則等專案上線後再整改工作量更大。
       如果你的查詢表中有大欄位或內容較多的欄位,如備註資訊、檔案內容等等,那在查詢表時一定要注意這方面的問題,否則可能會帶來嚴重的效能問題。如果表經常要查詢並且請求大內容欄位的概率很低,我們可以採用分表處理,將一個大表分拆成兩個一對一的關係表,將不常用的大內容欄位放在一張單獨的表中。如一張儲存上傳檔案的表:
       T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
       我們可以分拆成兩張一對一的關係表:
       T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
       T_FILECONTENT(ID, FILE_CONTENT)

       通過這種分拆,可以大大提少T_FILE表的單條記錄及總大小,這樣在查詢T_FILE時效能會更好,當需要查詢FILE_CONTENT欄位內容時再訪問T_FILECONTENT表。

3 減少互動次數

3.1 batch DML

       資料庫訪問框架一般都提供了批量提交的介面,jdbc支援batch的提交處理方法,當你一次性要往一個表中插入1000萬條資料時,如果採用普通的executeUpdate處理,那麼和伺服器互動次數為1000萬次,按每秒鐘可以向資料庫伺服器提交10000次估算,要完成所有工作需要1000秒。如果採用批量提交模式,1000條提交一次,那麼和伺服器互動次數為1萬次,互動次數大大減少。採用batch操作一般不會減少很多資料庫伺服器的物理IO,但是會大大減少客戶端與服務端的互動次數,從而減少了多次發起的網路延時開銷,同時也會降低資料庫的CPU開銷。

       假設要向一個普通表插入1000萬資料,每條記錄大小為1K位元組,表上沒有任何索引,客戶端與資料庫伺服器網路是100Mbps,以下是根據現在一般計算機能力估算的各種batch大小效能對比值:

 單位:ms

No batch

Batch=10

Batch=100

Batch=1000

Batch=10000

伺服器事務處理時間

0.1

0.1

0.1

0.1

0.1

伺服器IO處理時間

0.02

0.2

2

20

200

網路互動發起時間

0.1

0.1

0.1

0.1

0.1

網路資料傳輸時間

0.01

0.1

1

10

100

小計

0.23

0.5

3.2

30.2

300.2

平均每條記錄處理時間

0.23

0.05

0.032

0.0302

0.03002

       從上可以看出,Insert操作加大Batch可以對效能提高近8倍效能,一般根據主鍵的Update或Delete操作也可能提高2-3倍效能,但不如Insert明顯,因為Update及Delete操作可能有比較大的開銷在物理IO訪問。以上僅是理論計算值,實際情況需要根據具體環境測量。另外從測試角度來說,使用Batch也降低了併發性(畢竟Batch屬於事務操作)。

3.2 In List

       很多時候我們需要按一些ID查詢資料庫記錄,我們可以採用一個ID一個請求發給資料庫,如下所示:

       for:varin ids[] do begin

                select*from mytable whereid=:var;

       end;
       我們也可以做一個小的優化, 如下所示,用ID INLIST的這種方式寫SQL:

       select*frommytable whereidin(:id1,id2,...,idn);

       通過這樣處理可以大大減少SQL請求的數量,從而提高效能。那如果有10000個ID,那是不是全部放在一條SQL裡處理呢?答案肯定是否定的。首先大部份資料庫都會有SQL長度和IN裡個數的限制,如ORACLE的IN裡就不允許超過1000個值。
       另外當前資料庫一般都是採用基於成本的優化規則,當IN數量達到一定值時有可能改變SQL執行計劃,從索引訪問變成全表訪問,這將使效能急劇變化。隨著SQL中IN的裡面的值個數增加,SQL的執行計劃會更復雜,佔用的記憶體將會變大,這將會增加伺服器CPU及記憶體成本。
       評估在IN裡面一次放多少個值還需要考慮應用伺服器本地記憶體的開銷,有併發訪問時要計算本地資料使用週期內的併發上限,否則可能會導致記憶體溢位。

       綜合考慮,一般IN裡面的值個數超過20個以後效能基本沒什麼太大變化,也特別說明不要超過100,超過後可能會引起執行計劃的不穩定性及增加資料庫CPU及記憶體成本,這個需要專業DBA評估。

3.3 設定Fetch Size

       當我們採用select從資料庫查詢資料時,資料預設並不是一條一條返回給客戶端的,也不是一次全部返回客戶端的,而是根據客戶端fetch_size引數處理,每次只返回fetch_size條記錄,當客戶端遊標遍歷到尾部時再從服務端取資料,直到最後全部傳送完成。所以如果我們要從服務端一次取大量資料時,可以加大fetch_size,這樣可以減少結果資料傳輸的互動次數及伺服器資料準備時間,提高效能。

       以下是jdbc測試的程式碼,採用本地資料庫,表快取在資料庫CACHE中,因此沒有網路連線及磁碟IO開銷,客戶端只遍歷遊標,不做任何處理,這樣更能體現fetch引數的影響:

String vsql ="select * from t_employee";

PreparedStatementpstmt =conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(1000);

ResultSetrs = pstmt.executeQuery(vsql);

int cnt = rs.getMetaData().getColumnCount();

Object o;

while(rs.next()) {

    for(int i =1; i <= cnt; i++) {

       o = rs.getObject(i);

    }

}

       測試示例中的employee表有100000條記錄,每條記錄平均長度135位元組

       以下是測試結果,對每種fetchsize測試5次再取平均值:

etchsize

 elapse_time(s

1

20.516

2

11.34

4

6.894

8

4.65

16

3.584

32

2.865

64

2.656

128

2.44

256

2.765

512

3.075

1024

2.862

2048

2.722

4096

2.681

8192

2.715

       Oracle jdbc fetchsize預設值為10,由上測試可以看出fetchsize對效能影響還是比較大的,但是當fetchsize大於100時就基本上沒有影響了。fetchsize並不會存在一個最優的固定值,因為整體效能與記錄集大小及硬體平臺有關。根據測試結果建議當一次性要取大量資料時這個值設定為100左右,不要小於40。注意,fetchsize不能設定太大,如果一次取出的資料大於JVM的記憶體會導致記憶體溢位,所以建議不要超過1000,太大了也沒什麼效能提高,反而可能會增加記憶體溢位的危險。

       注:圖中fetchsize在128以後會有一些小的波動,這並不是測試誤差,而是由於resultset填充到具體對像時間不同的原因,由於resultset已經到本地記憶體裡了,所以估計是由於CPU的L1,L2 Cache命中率變化造成,由於變化不大,所以筆者也未深入分析原因。

3.4 使用儲存過程

       大型資料庫一般都支援儲存過程,合理的利用儲存過程也可以提高系統效能。如你有一個業務需要將A表的資料做一些加工然後更新到B表中,但是又不可能一條SQL完成,這時你需要如下3步操作:
       a:將A表資料全部取出到客戶端;
       b:計算出要更新的資料;
       c:將計算結果更新到B表。
       如果採用儲存過程你可以將整個業務邏輯封裝在儲存過程裡,然後在客戶端直接呼叫儲存過程處理,這樣可以減少網路互動的成本。
       當然,儲存過程也並不是十全十美,儲存過程有以下缺點:
       a、不可移植性,每種資料庫的內部程式設計語法都不太相同,當你的系統需要相容多種資料庫時最好不要用儲存過程。
       b、學習成本高,DBA一般都擅長寫儲存過程,但並不是每個程式設計師都能寫好儲存過程,除非你的團隊有較多的開發人員熟悉寫儲存過程,否則後期系統維護會產生問題。
       c、業務邏輯多處存在,採用儲存過程後也就意味著你的系統有一些業務邏輯不是在應用程式裡處理,這種架構會增加一些系統維護和除錯成本。
       d、儲存過程和常用應用程式語言不一樣,它支援的函式及語法有可能不能滿足需求,有些邏輯就只能通過應用程式處理。
       e、如果儲存過程中有複雜運算的話,會增加一些資料庫服務端的處理成本,對於集中式資料庫可能會導致系統可擴充套件性問題。
       f、為了提高效能,資料庫會把儲存過程程式碼編譯成中間執行程式碼(類似於java的class檔案),所以更像靜態語言。當儲存過程引用的對像(表、檢視等等)結構改變後,儲存過程需要重新編譯才能生效,在24*7高併發應用場景,一般都是線上變更結構的,所以在變更的瞬間要同時編譯儲存過程,這可能會導致資料庫瞬間壓力上升引起故障(Oracle資料庫就存在這樣的問題)。

       個人觀點:普通業務邏輯儘量不要使用儲存過程,定時性的ETL任務或報表統計函式可以根據團隊資源情況採用儲存過程處理。

3.5 優化業務邏輯

       要通過優化業務邏輯來提高效能是比較困難的,這需要程式設計師對所訪問的資料及業務流程非常清楚。

       舉個例子:12306網站的春運購票壓力很大,可以通過業務優化來解決部分問題,比如按客流量分散,一方面從時間上分散(允許提前兩個月訂票,將購買壓力分散到不同時間段),另一方面按客運段分佈(按省市、客戶段分佈售票,避免票源集中發售),還可以按需求差異分類處理(比如按T、K、G、D車型分類,一等座、二等座、硬座、臥鋪分類等)。

3.6 使用ResultSet遊標處理記錄

       現在大部分Java框架都是通過jdbc從資料庫取出資料,然後裝載到一個list裡再處理,list裡可能是業務Object,也可能是hashmap。
       由於JVM記憶體一般都小於4G,所以不可能一次通過sql把大量資料裝載到list裡。為了完成功能,很多程式設計師喜歡採用分頁的方法處理,如一次從資料庫取1000條記錄,通過多次迴圈搞定,保證不會引起JVM Out of memory問題。
       很多持久層框架(如iBatis)為了儘量讓程式設計師使用方便,封裝了jdbc通過statement執行資料返回到resultset的細節,導致程式設計師會想採用分頁的方式處理問題。實際上如果我們採用jdbc原始的resultset遊標處理記錄,在resultset迴圈讀取的過程中處理記錄,這樣就可以一次從資料庫取出所有記錄。顯著提高效能。

     這裡需要注意的是,採用resultset遊標處理記錄時,應該將遊標的開啟方式設定為FORWARD_READONLY模式(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY),否則會把結果快取在JVM裡,造成JVM Out of memory問題。以下程式碼示例:

String vsql ="select * from t_employee";

PreparedStatementpstmt =conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(100);

ResultSetrs = pstmt.executeQuery(vsql);

int col_cnt = rs.getMetaData().getColumnCount();

Object o;

while(rs.next()) {

         for(int j =1; j <= col_cnt; j++) {

                   o = rs.getObject(j);

         }

}

       以上程式碼實際執行時間為3.156秒,而採用分頁的方法處理實際需要6.516秒。效能提高了1倍多,如果採用分頁模式資料庫每次還需發生磁碟IO的話那效能可以提高更多。

4 減少資料庫伺服器CPU運算

4.1 使用繫結變數

       繫結變數是指SQL中對變化的值採用變數引數的形式提交,而不是在SQL中直接拼寫對應的值。
       非繫結變數寫法:Select * from employee where id=1234567
       繫結變數寫法:
       Select * from employee where id=?

       Preparestatement.setInt(1,1234567)

       Java中Preparestatement就是為處理繫結變數提供的對像,繫結變數有以下優點:
       1、防止SQL隱碼攻擊
       2、提高SQL可讀性
       3、提高SQL解析效能,不使用繫結變更我們一般稱為硬解析,使用繫結變數我們稱為軟解析。
       第1和第2點很好理解,做編碼的人應該都清楚,這裡不詳細說明。關於第3點,到底能提高多少效能呢,下面舉一個例子說明:
       假設有這個這樣的一個資料庫主機:
       2個4核CPU 
       100塊磁碟,每個磁碟支援IOPS為160
       業務應用的SQL如下:
       select * from table where pk=?
       這個SQL平均4個IO(3個索引IO+1個資料IO)
       IO快取命中率75%(索引全在記憶體中,資料需要訪問磁碟)
       SQL硬解析CPU消耗:1ms  (常用經驗值)

       SQL軟解析CPU消耗:0.02ms(常用經驗值)

       假設CPU每核效能是線性增長,訪問記憶體Cache中的IO時間忽略,要求計算系統對如上應用採用硬解析與採用軟解析支援的每秒最大併發數:

是否使用繫結變數

CPU支援最大併發數

磁碟IO支援最大併發數

不使用

2*4*1000=8000

100*160=16000

使用

2*4*1000/0.02=400000

100*160=16000

       從以上計算可以看出,不使用繫結變數的系統當併發達到8000時會在CPU上產生瓶頸,當使用繫結變數的系統當並行達到16000時會在磁碟IO上產生瓶頸。所以如果你的系統CPU有瓶頸時請先檢查是否存在大量的硬解析操作。

       使用繫結變數為何會提高SQL解析效能,這個需要從資料庫SQL執行原理說明,一條SQL在Oracle資料庫中的執行過程如下圖所示:


       當一條SQL傳送給資料庫伺服器後,系統首先會將SQL字串進行hash運算,得到hash值後再從伺服器記憶體裡的SQL快取區中進行檢索,如果有相同的SQL字元,並且確認是同一邏輯的SQL語句,則從共享池快取中取出SQL對應的執行計劃,根據執行計劃讀取資料並返回結果給客戶端。

       如果在共享池中未發現相同的SQL則根據SQL邏輯生成一條新的執行計劃並儲存在SQL快取區中,然後根據執行計劃讀取資料並返回結果給客戶端。

       為了更快的檢索SQL是否在快取區中,首先進行的是SQL字串hash值對比,如果未找到則認為沒有快取,如果存在再進行下一步的準確對比,所以要命中SQL快取區應保證SQL字元是完全一致,中間有大小寫或空格都會認為是不同的SQL。

       如果我們不採用繫結變數,採用字串拼接的模式生成SQL,那麼每條SQL都會產生執行計劃,這樣會導致共享池耗盡,快取命中率也很低。

       一些無需使用繫結變數的場景:
       a、資料倉儲應用,這種應用一般併發不高,但是每個SQL執行時間很長,SQL解析的時間相比SQL執行時間比較小,繫結變數對效能提高不明顯。資料倉儲一般都是內部分析應用,所以也不太會發生SQL隱碼攻擊的安全問題。
       b、資料分佈不均勻的特殊邏輯,如產品表,記錄有1億,有一產品狀態欄位,上面建有索引,有稽核中,稽核通過,稽核未通過3種狀態,其中稽核通過9500萬,稽核中1萬,稽核不通過499萬。
       要做這樣一個查詢:
       select count(*) from product where status=?
       採用繫結變數的話,那麼只會有一個執行計劃,如果走索引訪問,那麼對於稽核中查詢很快,對稽核通過和稽核不通過會很慢;如果不走索引,那麼對於稽核中與稽核通過和稽核不通過時間基本一樣;
       對於這種情況應該不使用繫結變數,而直接採用字元拼接的方式生成SQL,這樣可以為每個SQL生成不同的執行計劃,如下所示。
       select count(*) from product where status='approved'; //不使用索引(稽核通過)
       select count(*) from product where status='tbd'; //不使用索引(稽核未通過)

       select count(*) from product where status='auditing';//使用索引(稽核中)

4.2 合理使用排序

       Oracle的排序演算法一直在優化,但是總體時間複雜度約等於nLog(n)。普通OLTP系統排序操作一般都是在記憶體裡進行的,對於資料庫來說是一種CPU的消耗,曾在PC機做過測試,單核普通CPU在1秒鐘可以完成100萬條記錄的全記憶體排序操作,所以說由於現在CPU的效能增強,對於普通的幾十條或上百條記錄排序對系統的影響也不會很大。但是當你的記錄集增加到上萬條以上時,你需要注意是否一定要這麼做了,大記錄集排序不僅增加了CPU開銷,而且可能會由於記憶體不足發生硬碟排序的現象,當發生硬碟排序時效能會急劇下降,這種需求需要與DBA溝通再決定,取決於你的需求和資料,所以只有你自己最清楚,而不要被別人說排序很慢就嚇倒。
       以下列出了可能會發生排序操作的SQL語法:
       Order by
       Group by
       Distinct
       Exists子查詢
       Not Exists子查詢
       In子查詢
       Not In子查詢
       Union(並集),Union All也是一種並集操作,但是不會發生排序,如果你確認兩個資料集不需要執行去除重複資料操作,那請使用Union All 代替Union。
       Minus(差集)
       Intersect(交集)
       Create Index

       Merge Join,這是一種兩個表連線的內部演算法,執行時會把兩個表先排序好再連線,應用於兩個大表連線的操作。如果你的兩個表連線的條件都是等值運算,那可以採用Hash Join來提高效能,因為Hash Join使用Hash 運算來代替排序的操作。具體原理及設定參考SQL執行計劃優化專題。

4.3 減少比較操作

       我們SQL的業務邏輯經常會包含一些比較操作,如a=b,a<b之類的操作,對於這些比較運算元據庫都體現得很好,但是如果有以下操作,我們需要保持警惕:
       Like模糊查詢,如下所示:
       a like ‘%abc%’
       Like模糊查詢對於資料庫來說不是很擅長,特別是你需要模糊檢查的記錄有上萬條以上時,效能比較糟糕,這種情況一般可以採用專用Search或者採用全文索引方案來提高效能。
       不能使用索引定位的大量In List,如下所示:
       a in (:1,:2,:3,…,:n)   ----n>20
       如果這裡的a欄位不能通過索引比較,那資料庫會將欄位與in裡面的每個值都進行比較運算,如果記錄數有上萬以上,會明顯感覺到SQL的CPU開銷加大,這個情況有兩種解決方式:
       a、將in列表裡面的資料放入一張中間小表,採用兩個表Hash Join關聯的方式處理;
       b、採用str2varList方法將欄位串列表轉換一個臨時表處理,關於str2varList方法可以在網上直接查詢,這裡不詳細介紹。
       以上兩種解決方案都需要與中間表Hash Join的方式才能提高效能,如果採用了Nested Loop的連線方式效能會更差。

       如果發現我們的系統IO沒問題但是CPU負載很高,就有可能是上面的原因,這種情況不太常見,如果遇到了最好能和DBA溝通並確認準確的原因。

4.4 大量複雜運算在客戶端處理

       什麼是複雜運算,一般我認為是一秒鐘CPU只能做10萬次以內的運算。如含小數的對數及指數運算、三角函式、3DES及BASE64資料加密演算法等等。

       如果有大量這類函式運算,儘量放在客戶端處理,一般CPU每秒中也只能處理1萬-10萬次這樣的函式運算,放在資料庫內不利於高併發處理。

5 利用更多的資源

5.1 客戶端多程式並行訪問

       多程式並行訪問是指在客戶端建立多個程式(執行緒),每個程式建立一個與資料庫的連線,然後同時向資料庫提交訪問請求。當資料庫主機資源有空閒時,我們可以採用客戶端多程式並行訪問的方法來提高效能。如果資料庫主機已經很忙時,採用多程式並行訪問效能不會提高,反而可能會更慢。所以使用這種方式最好與DBA或系統管理員進行溝通後再決定是否採用。
       例如:
       我們有10000個產品ID,現在需要根據ID取出產品的詳細資訊,如果單執行緒訪問,按每個IO要5ms計算,忽略主機CPU運算及網路傳輸時間,我們需要50s才能完成任務。如果採用5個並行訪問,每個程式訪問2000個ID,那麼10s就有可能完成任務。
       那是不是並行數越多越好呢,開1000個並行是否只要50ms就搞定,答案肯定是否定的,當並行數超過伺服器主機資源的上限時效能就不會再提高,如果再增加反而會增加主機的程式間排程成本和程式衝突機率。
       以下是一些如何設定並行數的基本建議:
       如果瓶頸在伺服器主機,但是主機還有空閒資源,那麼最大並行數取主機CPU核數和主機提供資料服務的磁碟數兩個引數中的最小值,同時要保證主機有資源做其它任務。
       如果瓶頸在客戶端處理,但是客戶端還有空閒資源,那建議不要增加SQL的並行,而是用一個程式取回資料後在客戶端起多個程式處理即可,程式數根據客戶端CPU核數計算。
       如果瓶頸在客戶端網路,那建議做資料壓縮或者增加多個客戶端,採用map reduce的架構處理。

       如果瓶頸在伺服器網路,那需要增加伺服器的網路頻寬或者在服務端將資料壓縮後再處理了。

5.2 資料庫並行處理

       資料庫並行處理是指客戶端一條SQL的請求,資料庫內部自動分解成多個程式並行處理,如下圖所示:


       並不是所有的SQL都可以使用並行處理,一般只有對錶或索引進行全部訪問時才可以使用並行。資料庫表預設是不開啟並行訪問,所以需要指定SQL並行的提示,如下所示:
       select /*+parallel(a,4)*/ * from employee;
       並行的優點:
       使用多程式處理,充分利用資料庫主機資源(CPU,IO),提高效能。
       並行的缺點:
       1、單個會話佔用大量資源,影響其它會話,所以只適合在主機負載低時期使用;
       2、只能採用直接IO訪問,不能利用快取資料,所以執行前會觸發將髒快取資料寫入磁碟操作。
       注:
       1、並行處理在OLTP類系統中慎用,使用不當會導致一個會話把主機資源全部佔用,而正常事務得不到及時響應,所以一般只是用於資料倉儲平臺或大資料處理平臺。
       2、一般對於百萬級記錄以下的小表採用並行訪問效能並不能提高,反而可能會讓效能更差。

相關文章