百萬級數據庫優化方案


一、百萬級數據庫優化方案

 

1.對查詢進行優化,要盡量避免全表掃描,首先應考慮在 where order by 涉及的列上建立索引。

2.應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:

select id from t where num is null

最好不要給數據庫留NULL,盡可能的使用 NOT NULL填充數據庫.

備注、描述、評論之類的可以設置為 NULL,其他的,最好不要使用NULL

不要以為 NULL 不需要空間,比如:char(100) 型,在字段建立時,空間就固定了, 不管是否插入值(NULL也包含在內),都是占用 100個字符的空間的,如果是varchar這樣的變長字段, null 不占用空間。


可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢:

select id from t where num = 0


3.
應盡量避免在 where 子句中使用 != <> 操作符,否則將引擎放棄使用索引而進行全表掃描。

4.
應盡量避免在 where 子句中使用 or 來連接條件,如果一個字段有索引,一個字段沒有索引,將導致引擎放棄使用索引而進行全表掃描,如:

select id from t where num=10 or Name = 'admin'

可以這樣查詢:

select id from t where num = 10union allselect id from t where Name = 'admin'


5.in
not in 也要慎用,否則會導致全表掃描,如:

select id from t where num in(1,2,3)

對於連續的數值,能用 between 就不要用 in

select id from t where num between 1 and 3

很多時候用 exists 代替 in 是一個好的選擇:

select num from a where num in(select num from b)

用下面的語句替換:

select num from a where exists(select 1 from b where num=a.num)

   

6.下面的查詢也將導致全表掃描:

select id from t where name like '%abc%'

若要提高效率,可以考慮全文檢索。

7.
如果在 where 子句中使用參數,也會導致全表掃描。因為SQL只有在運行時才會解析局部變量,但優化程序不能將訪問計划的選擇推遲到運行時;它必須在編譯時進行選擇。然 而,如果在編譯時建立訪問計划,變量的值還是未知的,因而無法作為索引選擇的輸入項。如下面語句將進行全表掃描:

select id from t where num = @num

可以改為強制查詢使用索引:

select id from t with(index(索引名)) where num = @num

.應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where num/2 = 100

應改為:

select id from t where num = 100*2


9.
應盡量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where substring(name,1,3) = 'abc' -–name以abc開頭的idselect id from t where datediff(day,createdate,'2005-11-30′) = 0    -–'2005-11-30' --生成的id

應改為:

select id from t where name like 'abc%'select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'


10.
不要在 where 子句中的"="左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。

11.
在使用索引字段作為條件時,如果該索引是復合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應盡可能的讓字段順序與索引順序相一致。

12.
不要寫一些沒有意義的查詢,如需要生成一個空表結構:

select col1,col2 into #t from t where 1=0

這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
create table #t(…)

13.Update 語句,如果只更改12個字段,不要Update全部字段,否則頻繁調用會引起明顯的性能消耗,同時帶來大量日志。

14.
對於多張大數據量(這里幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差。

15.select count(*) from table
;這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。


16.
索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert update 的效率,因為 insert update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。

17.
應盡可能的避免更新 clustered 索引數據列,因為 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那么需要考慮是否應將該索引建為 clustered 索引。

18.
盡量使用數字型字段,若只含數值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因為引擎在處理查詢和連 接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。

19.
盡可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。

20.
任何地方都不要使用 select * from t ,用具體的字段列表代替"*"不要返回用不到的任何字段

21.
盡量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。

22.
避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重復引用大型表或常用表中的某個數據集時。但是,對於一次性事件, 最好使用導出表。

23.
在新建臨時表時,如果一次性插入數據量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,為了緩和系統表的資源,應先create table,然后insert

24.
如果使用到了臨時表,在存儲過程的最后務必將所有的臨時表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統表的較長時間鎖定。

25.
盡量避免使用游標,因為游標的效率較差,如果游標操作的數據超過1萬行,那么就應該考慮改寫。

26.
使用基於游標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。

27.
與臨時表一樣,游標並不是不可使用。對小型數據集使用 FAST_FORWARD 游標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括"合計"的例程通常要比使用游標執行的速度快。如果開發時 間允許,基於游標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。

28.
在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。無需在執行存儲過程和觸發器的每個語句后向客戶端發送 DONE_IN_PROC 消息。

29.
盡量避免大事務操作,提高系統並發能力。

30.
盡量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。

   

實際案例分析:拆分大的 DELETE INSERT 語句,批量提交SQL語句
  如果你需要在一個在線的網站上去執行一個大的 DELETE  INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。
  Apache 會有很多的子進程或線程。所以,其工作起來相當有效率,而我們的服務器也不希望有太多的子進程,線程和數據庫鏈接,這是極大的占服務器資源的事情,尤其是內存。
  如果你把你的表鎖上一段時間,比如30秒鍾,那么對於一個有很高訪問量的站點來說,這30秒所積累的訪問進程/線程,數據庫鏈接,打開的文件數,可能不僅僅會讓你的WEB服務崩潰,還可能會讓你的整台服務器馬上掛了。
  所以,如果你有一個大的處理,你一定把其拆分,使用 LIMIT Oracle(rownum),sqlserver(top)條件是一個好的方法。下面是一個MySQL示例:

while(1){

 

  //每次只做1000條   mysql_query("delete from logs where log_date <= '2012-11-01' limit 1000");

 

  if(mysql_affected_rows() == 0){

 

     //刪除完成,退出!     break

  }//每次暫停一段時間,釋放表讓其他進程/線程訪問。usleep(50000)

 

}

二、數據庫訪問性能優化

 

特別說明:

1  本文只是面對數據庫應用開發的程序員,不適合專業DBADBA在數據庫性能優化方面需要了解更多的知識;

2  本文許多示例及概念是基於Oracle數據庫描述,對於其它關系型數據庫也可以參考,但許多觀點不適合於KV數據庫或內存數據庫或者是基於SSD技術的數據庫;

3  本文未深入數據庫優化中最核心的執行計划分析技術。

   

讀者對像:

開發人員:如果你是做數據庫開發,那本文的內容非常適合,因為本文是從程序員的角度來談數據庫性能優化。

架構師:如果你已經是數據庫應用的架構師,那本文的知識你應該清楚90%,否則你可能是一個喜歡折騰的架構師。

DBA(數據庫管理員):大型數據庫優化的知識非常復雜,本文只是從程序員的角度來談性能優化,DBA除了需要了解這些知識外,還需要深入數據庫的內部體系架構來解決問題。

   

     在網上有很多文章介紹數據庫優化知識,但是大部份文章只是對某個一個方面進行說明,而對於我們程序員來說這種介紹並不能很好的掌握優化知識,因為很多介紹只是對一些特定的場景優化的,所以反而有時會產生誤導或讓程序員感覺不明白其中的奧妙而對數據庫優化感覺很神秘。

     很多程序員總是問如何學習數據庫優化,有沒有好的教材之類的問題。在書店也看到了許多數據庫優化的專業書籍,但是感覺更多是面向DBA或者是PL/SQL開發方面的知識,個人感覺不太適合普通程序員。而要想做到數據庫優化的高手,不是花幾周,幾個月就能達到的,這並不是因為數據庫優化有多高深,而是因為要做好優化一方面需要有非常好的技術功底,對操作系統、存儲硬件網絡、數據庫原理等方面有比較扎實的基礎知識,另一方面是需要花大量時間對特定的數據庫進行實踐測試與總結。

     作為一個程序員,我們也許不清楚線上正式的服務器硬件配置,我們不可能像DBA那樣專業的對數據庫進行各種實踐測試與總結,但我們都應該非常了解我們SQL的業務邏輯,我們清楚SQL中訪問表及字段的數據情況,我們其實只關心我們的SQL是否能盡快返回結果。那程序員如何利用已知的知識進行數據庫優化?如何能快速定位SQL性能問題並找到正確的優化方向?

面對這些問題,筆者總結了一些面向程序員的基本優化法則,本文將結合實例來坦述數據庫開發的優化知識。

     要正確的優化SQL,我們需要快速定位能性的瓶頸點,也就是說快速找到我們SQL主要的開銷在哪里?而大多數情況性能最慢的設備會是瓶頸點,如下載時網絡速度可能會是瓶頸點,本地復制文件時硬盤可能會是瓶頸點,為什么這些一般的工作我們能快速確認瓶頸點呢,因為我們對這些慢速設備的性能數據有一些基本的認識,如網絡帶寬是2Mbps,硬盤是每分鍾7200轉等等。因此,為了快速找到SQL的性能瓶頸點,我們也需要了解我們計算機系統的硬件基本性能指標,下圖展示的當前主流計算機性能指標數據。

從圖上可以看到基本上每種設備都有兩個指標:

延時(響應時間):表示硬件的突發處理能力;

帶寬(吞吐量):代表硬件持續處理能力。

   

從上圖可以看出,計算機系統硬件性能從高到代依次為:

CPU——Cache(L1-L2-L3)——內存——SSD硬盤——網絡——硬盤

由於SSD硬盤還處於快速發展階段,所以本文的內容不涉及SSD相關應用系統。

根據數據庫知識,我們可以列出每種硬件主要的工作內容:

CPU及內存:緩存數據訪問、比較、排序、事務檢測、SQL解析、函數或邏輯運算;

網絡:結果數據傳輸、SQL請求、遠程數據庫訪問(dblink);

硬盤:數據訪問、數據寫入、日志記錄、大數據量排序、大表連接。

   

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

這個優化法則歸納為5個層次:

1  減少數據訪問(減少磁盤訪問)

2  返回更少數據(減少網絡傳輸或磁盤訪問)

3  減少交互次數(減少網絡傳輸)

4  減少服務器CPU開銷(減少CPU及內存開銷)

5  利用更多資源(增加資源)

   

由於每一層優化法則都是解決其對應硬件的性能問題,所以帶來的性能提升比例也不一樣。傳統數據庫系統設計是也是盡可能對低速設備提供優化方法,因此針對低速設備問題的可優化手段也更多,優化成本也更低。我們任何一個SQL的性能優化都應該按這個規則由上到下來診斷問題並提出解決方案,而不應該首先想到的是增加資源解決問題。

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

優化法則

性能提升效果

優化成本

減少數據訪問

1~1000

返回更少數據

1~100

減少交互次數

1~20

減少服務器CPU開銷

1~5

利用更多資源

@~10


 

接下來,我們針對5種優化法則列舉常用的優化手段並結合實例分析。

 

 

接下來,我們針對5種優化法則列舉常用的優化手段並結合實例分析。

 

二、oracle數據庫兩個基本概念


數據塊是數據庫中數據在磁盤中存儲的最小單位,也是一次IO訪問的最小單位,一個數據塊通常可以存儲多條記錄,數據塊大小是DBA在創建數據庫或表空間時指定,可指定為2K4K8K16K32K字節。下圖是一個Oracle數據庫典型的物理結構,一個數據庫可以包括多個數據文件,一個數據文件內又包含多個數據塊;

ROWID是每條記錄在數據庫中的唯一標識,通過ROWID可以直接定位記錄到對應的文件號及數據塊位置。ROWID內容包括文件號、對像號、數據塊號、記錄槽號,如下圖所示:

三、數據庫訪問優化法則詳解

減少數據訪問

創建並使用正確的索引

 

數據庫索引的原理非常簡單,但在復雜的表中真正能正確使用索引的人很少,即使是專業的DBA也不一定能完全做到最優。

索引會大大增加表記錄的DML(INSERT,UPDATE,DELETE)開銷,正確的索引可以讓性能提升1001000倍以上,不合理的索引也可能會讓性能下降100倍,因此在一個表中創建什么樣的索引需要平衡各種業務需求。

索引常見問題:

索引有哪些種類?

常見的索引有B-TREE索引、位圖索引、全文索引,位圖索引一般用於數據倉庫應用,全文索引由於使用較少,這里不深入介紹。B-TREE索引包括很多擴展類型,如組合索引、反向索引、函數索引等等,以下是B-TREE索引的簡單介紹:

B-TREE索引也稱為平衡樹索引(Balance Tree),它是一種按字段排好序的樹形目錄結構,主要用於提升查詢性能和唯一約束支持。B-TREE索引的內容包括根節點、分支節點、葉子節點。

葉子節點內容:索引字段內容+表記錄ROWID

根節點,分支節點內容:當一個數據塊中不能放下所有索引字段數據時,就會形成樹形的根節點或分支節點,根節點與分支節點保存了索引樹的順序及各層級間的引用關系。

         一個普通的BTREE索引結構示意圖如下所示:

如果我們把一個表的內容認為是一本字典,那索引就相當於字典的目錄,如下圖所示:

圖中是一個字典按部首+筆划數的目錄,相當於給字典建了一個按部首+筆划的組合索引。

一個表中可以建多個索引,就如一本字典可以建多個目錄一樣(按拼音、筆划、部首等等)。

一個索引也可以由多個字段組成,稱為組合索引,如上圖就是一個按部首+筆划的組合目錄。

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_CONTENT

   

   

如何知道SQL是否使用了正確的索引?

簡單SQL可以根據索引使用語法規則判斷,復雜的SQL不好辦,判斷SQL的響應時間是一種策略,但是這會受到數據量、主機負載及緩存等因素的影響,有時數據全在緩存里,可能全表訪問的時間比索引訪問時間還少。要准確知道索引是否正確使用,需要到數據庫中查看SQL真實的執行計划,這個話題比較復雜,詳見SQL執行計划專題介紹。

   

索引對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性能。

目前ORACLESQL執行計划的算法約300種,而且一直在增加,所以SQL執行計划是一個非常復雜的課題,一個普通DBA能掌握50種就很不錯了,就算是資深DBA也不可能把每個執行計划的算法描述清楚。雖然有這么多種算法,但並不表示我們無法優化執行計划,因為我們常用的SQL執行計划算法也就十幾個,如果一個程序員能把這十幾個算法搞清楚,那就掌握了80%SQL執行計划調優知識。

由於篇幅的原因,SQL執行計划需要專題介紹,在這里就不多說了。

   

2、返回更少的數據

2.1、數據分頁處理

一般數據分頁方式有:

2.1.1、客戶端(應用程序或瀏覽器)分頁

將數據從應用服務器全部下載到本地應用程序或瀏覽器,在應用程序或瀏覽器內部通過本地代碼進行分頁處理

優點:編碼簡單,減少客戶端與應用服務器網絡交互次數

缺點:首次交互時間長,占用客戶端內存

適應場景:客戶端與應用服務器網絡延時較大,但要求后續操作流暢,如手機GPRS,超遠程訪問(跨國)等等。

2.1.2、應用服務器分頁

將數據從數據庫服務器全部下載到應用服務器,在應用服務器內部再進行數據篩選。以下是一個應用服務器端Java程序分頁的示例:

List list=executeQuery("select * from employee order by id");

Int count= list.size();

List subList= list.subList(10, 20);

   

優點:編碼簡單,只需要一次SQL交互,總數據與分頁數據差不多時性能較好。

缺點:總數據量較多時性能較差。

適應場景:數據庫系統不支持分頁處理,數據量較小並且可控。

   

2.1.3、數據庫SQL分頁

采用數據庫SQL分頁需要兩次SQL完成

一個SQL計算總數量

一個SQL返回分頁后的數據

優點:性能好

缺點:編碼復雜,各種數據庫語法不同,需要兩次SQL交互。

   

oracle數據庫一般采用rownum來進行分頁,常用分頁語法有如下兩種:

   

直接通過rownum分頁:

select * from (

         select a.*,rownum rn from

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

         where rownum<=20)

where rn>10;

數據訪問開銷=索引IO+索引全部記錄結果對應的表數據IO

   

采用rowid分頁語法

優化原理是通過純索引找出分頁記錄的ROWID,再通過ROWID回表返回數據,要求內層查詢和排序字段全在索引里。

create index myindex on product(company_id,status);

   

select b.* from (

         select * from (

                   select a.*,rownum rn from

                            (select rowid rid,status from product a where company_id=? order by status) a

                   where rownum<=20)

         where rn>10) a, product b

where a.rid=b.rowid;

數據訪問開銷=索引IO+索引分頁結果對應的表數據IO

   

實例:

一個公司產品有1000條記錄,要分頁取其中20個產品,假設訪問公司索引需要50IO2條記錄需要1個表數據IO

那么按第一種ROWNUM分頁寫法,需要550(50+1000/2)IO,按第二種ROWID分頁寫法,只需要60IO(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_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT

我們可以分拆成兩張一對一的關系表:

T_FILEID,FILE_NAME,FILE_SIZE,FILE_TYPE

T_FILECONTENTID, FILE_CONTENT

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

   

3、減少交互次數

3.1batch 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倍性能,一般根據主鍵的UpdateDelete操作也可能提高2-3倍性能,但不如Insert明顯,因為UpdateDelete操作可能有比較大的開銷在物理IO訪問。以上僅是理論計算值,實際情況需要根據具體環境測量。

   

3.2In List

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

for :var in ids[] do begin

  select * from mytable where id=:var;

end;

   

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

select * from mytable where id in(:id1,id2,...,idn);

   

通過這樣處理可以大大減少SQL請求的數量,從而提高性能。那如果有10000ID,那是不是全部放在一條SQL里處理呢?答案肯定是否定的。首先大部份數據庫都會有SQL長度和IN里個數的限制,如ORACLEIN里就不允許超過1000個值

另外當前數據庫一般都是采用基於成本的優化規則,當IN數量達到一定值時有可能改變SQL執行計划,從索引訪問變成全表訪問,這將使性能急劇變化。隨着SQLIN的里面的值個數增加,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";

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

pstmt.setFetchSize(1000);

ResultSet rs = 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次再取平均值:

   

fetchsize

 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,太大了也沒什么性能提高,反而可能會增加內存溢出的危險。

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

   

iBatisSqlMapping配置文件可以對每個SQL語句指定fetchsize大小,如下所示:

   

<select id="getAllProduct" resultMap="HashMap" fetchSize="1000">

select * from employee

</select>

   

3.4、使用存儲過程

大型數據庫一般都支持存儲過程,合理的利用存儲過程也可以提高系統性能。如你有一個業務需要將A表的數據做一些加工然后更新到B表中,但是又不可能一條SQL完成,這時你需要如下3步操作:

a:將A表數據全部取出到客戶端;

b:計算出要更新的數據;

c:將計算結果更新到B表。

   

如果采用存儲過程你可以將整個業務邏輯封裝在存儲過程里,然后在客戶端直接調用存儲過程處理,這樣可以減少網絡交互的成本。

當然,存儲過程也並不是十全十美,存儲過程有以下缺點:

a、不可移植性,每種數據庫的內部編程語法都不太相同,當你的系統需要兼容多種數據庫時最好不要用存儲過程。

b、學習成本高,DBA一般都擅長寫存儲過程,但並不是每個程序員都能寫好存儲過程,除非你的團隊有較多的開發人員熟悉寫存儲過程,否則后期系統維護會產生問題。

c、業務邏輯多處存在,采用存儲過程后也就意味着你的系統有一些業務邏輯不是在應用程序里處理,這種架構會增加一些系統維護和調試成本。

d、存儲過程和常用應用程序語言不一樣,它支持的函數及語法有可能不能滿足需求,有些邏輯就只能通過應用程序處理。

e、如果存儲過程中有復雜運算的話,會增加一些數據庫服務端的處理成本,對於集中式數據庫可能會導致系統可擴展性問題。

f、為了提高性能,數據庫會把存儲過程代碼編譯成中間運行代碼(類似於javaclass文件),所以更像靜態語言。當存儲過程引用的對像(表、視圖等等)結構改變后,存儲過程需要重新編譯才能生效,在24*7高並發應用場景,一般都是在線變更結構的,所以在變更的瞬間要同時編譯存儲過程,這可能會導致數據庫瞬間壓力上升引起故障(Oracle數據庫就存在這樣的問題)

   

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

   

3.5、優化業務邏輯

要通過優化業務邏輯來提高性能是比較困難的,這需要程序員對所訪問的數據及業務流程非常清楚。

舉一個案例:

某移動公司推出優惠套參,活動對像為VIP會員並且2010123月平均話費20元以上的客戶。

那我們的檢測邏輯為:

select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

select vip_flag from member where phone_no='13988888888';

if avg_money>20 and vip_flag=true then

begin

  執行套參();

end;

   

如果我們修改業務邏輯為:

select avg(money) as  avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

if avg_money>20 then

begin

  select vip_flag from member where phone_no='13988888888';

  if vip_flag=true then

  begin

    執行套參();

  end;

end;

通過這樣可以減少一些判斷vip_flag的開銷,平均話費20元以下的用戶就不需要再檢測是否VIP了。

   

如果程序員分析業務,VIP會員比例為1%,平均話費20元以上的用戶比例為90%,那我們改成如下:

select vip_flag from member where phone_no='13988888888';

if vip_flag=true then

begin

  select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

  if avg_money>20 then

  begin

    執行套參();

  end;

end;

這樣就只有1%VIP會員才會做檢測平均話費,最終大大減少了SQL的交互次數。

   

以上只是一個簡單的示例,實際的業務總是比這復雜得多,所以一般只是高級程序員更容易做出優化的邏輯,但是我們需要有這樣一種成本優化的意識。

   

3.6、使用ResultSet游標處理記錄

現在大部分Java框架都是通過jdbc從數據庫取出數據,然后裝載到一個list里再處理,list里可能是業務Object,也可能是hashmap

由於JVM內存一般都小於4G,所以不可能一次通過sql把大量數據裝載到list里。為了完成功能,很多程序員喜歡采用分頁的方法處理,如一次從數據庫取1000條記錄,通過多次循環搞定,保證不會引起JVM Out of memory問題。

   

以下是實現此功能的代碼示例,t_employee表有10萬條記錄,設置分頁大小為1000

   

d1 = Calendar.getInstance().getTime();

vsql = "select count(*) cnt from t_employee";

pstmt = conn.prepareStatement(vsql);

ResultSet rs = pstmt.executeQuery();

Integer cnt = 0;

while (rs.next()) {

         cnt = rs.getInt("cnt");

}

Integer lastid=0;

Integer pagesize=1000;

System.out.println("cnt:" + cnt);

String vsql = "select count(*) cnt from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql);

ResultSet rs = pstmt.executeQuery();

Integer cnt = 0;

while (rs.next()) {

         cnt = rs.getInt("cnt");

}

Integer lastid = 0;

Integer pagesize = 1000;

System.out.println("cnt:" + cnt);

for (int i = 0; i <= cnt / pagesize; i++) {

         vsql = "select * from (select * from t_employee where id>? order by id) where rownum<=?";

         pstmt = conn.prepareStatement(vsql);

         pstmt.setFetchSize(1000);

         pstmt.setInt(1, lastid);

         pstmt.setInt(2, pagesize);

         rs = pstmt.executeQuery();

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

         Object o;

         while (rs.next()) {

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

                            o = rs.getObject(j);

                   }

                   lastid = rs.getInt("id");

         }

         rs.close();

         pstmt.close();

}

   

以上代碼實際執行時間為6.516

   

很多持久層框架為了盡量讓程序員使用方便,封裝了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";

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

pstmt.setFetchSize(100);

ResultSet rs = 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

   

從測試結果可以看出性能提高了1倍多,如果采用分頁模式數據庫每次還需發生磁盤IO的話那性能可以提高更多。

iBatis等持久層框架考慮到會有這種需求,所以也有相應的解決方案,在iBatis里我們不能采用queryForList的方法,而應用該采用queryWithRowHandler加回調事件的方式處理,如下所示:

   

MyRowHandler myrh=new MyRowHandler();

sqlmap.queryWithRowHandler("getAllEmployee", myrh);

   

class MyRowHandler implements RowHandler {

    public void handleRow(Object o) {

       //todo something

    }

}

   

iBatisqueryWithRowHandler很好的封裝了resultset遍歷的事件處理,效果及性能與resultset遍歷一樣,也不會產生JVM內存溢出。

   

4、減少數據庫服務器CPU運算

4.1、使用綁定變量

綁定變量是指SQL中對變化的值采用變量參數的形式提交,而不是在SQL中直接拼寫對應的值。

非綁定變量寫法:Select * from employee where id=1234567

綁定變量寫法:

Select * from employee where id=?

Preparestatement.setInt(1,1234567)

   

JavaPreparestatement就是為處理綁定變量提供的對像,綁定變量有以下優點:

1、防止SQL注入

2、提高SQL可讀性

3、提高SQL解析性能,不使用綁定變更我們一般稱為硬解析,使用綁定變量我們稱為軟解析。

1和第2點很好理解,做編碼的人應該都清楚,這里不詳細說明。關於第3點,到底能提高多少性能呢,下面舉一個例子說明:

   

假設有這個這樣的一個數據庫主機:

24CPU 

100塊磁盤,每個磁盤支持IOPS160

業務應用的SQL如下:

select * from table where pk=?

這個SQL平均4IO3個索引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執行原理說明,一條SQLOracle數據庫中的執行過程如下圖所示:

當一條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機做過測試,單核普通CPU1秒鍾可以完成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=ba<b之類的操作,對於這些比較操作數據庫都體現得很好,但是如果有以下操作,我們需要保持警惕:

Like模糊查詢,如下所示:

a like '%abc%'

   

Like模糊查詢對於數據庫來說不是很擅長,特別是你需要模糊檢查的記錄有上萬條以上時,性能比較糟糕,這種情況一般可以采用專用Search或者采用全文索引方案來提高性能。

不能使用索引定位的大量In List,如下所示:

a in (:1,:2,:3,…,:n)   ----n>20

如果這里的a字段不能通過索引比較,那數據庫會將字段與in里面的每個值都進行比較運算,如果記錄數有上萬以上,會明顯感覺到SQLCPU開銷加大,這個情況有兩種解決方式:

a、  in列表里面的數據放入一張中間小表,采用兩個表Hash Join關聯的方式處理;

b、  采用str2varList方法將字段串列表轉換一個臨時表處理,關於str2varList方法可以在網上直接查詢,這里不詳細介紹。

   

以上兩種解決方案都需要與中間表Hash Join的方式才能提高性能,如果采用了Nested Loop的連接方式性能會更差。

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

   

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

什么是復雜運算,一般我認為是一秒鍾CPU只能做10萬次以內的運算。如含小數的對數及指數運算、三角函數、3DESBASE64數據加密算法等等。

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

   

5、利用更多的資源

5.1、客戶端多進程並行訪問

多進程並行訪問是指在客戶端創建多個進程(線程),每個進程建立一個與數據庫的連接,然后同時向數據庫提交訪問請求。當數據庫主機資源有空閑時,我們可以采用客戶端多進程並行訪問的方法來提高性能。如果數據庫主機已經很忙時,采用多進程並行訪問性能不會提高,反而可能會更慢。所以使用這種方式最好與DBA或系統管理員進行溝通后再決定是否采用。

   

例如:

我們有10000個產品ID,現在需要根據ID取出產品的詳細信息,如果單線程訪問,按每個IO5ms計算,忽略主機CPU運算及網絡傳輸時間,我們需要50s才能完成任務。如果采用5個並行訪問,每個進程訪問2000ID,那么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、一般對於百萬級記錄以下的小表采用並行訪問性能並不能提高,反而可能會讓性能更差。

 

 

全文都是轉載,作者地址https://mp.weixin.qq.com/s?__biz=MzIxMjg4NDU1NA==&mid=2247483684&idx=1&sn=f5abc60e696b2063e43cd9ccb40df101&chksm=97be0c01a0c98517029ff9aa280b398ab5c81fa1fcfe0e746222a3bfe75396d9eea1e249af38&mpshare=1&scene=1&srcid=0606XGHeBS4RBZloVv786wBY#rd

如果有侵權請,通知好刪除


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM