1. 程式人生 > >資料庫SQL優化大總結1之- 百萬級資料庫優化方案

資料庫SQL優化大總結1之- 百萬級資料庫優化方案

一、百萬級資料庫優化方案

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 = 10 union 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 wherenum = 100*2


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

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

應改為:

  1. select id from t where name like 'abc%'
  2. 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 語句,如果只更改1、2個欄位,不要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示例:

  1. while(1){
  2.   //每次只做1000條   mysql_query(“delete from logs where log_date <= ’2012-11-01limit 1000”);
  3.   if(mysql_affected_rows() == 0){
  4.      //刪除完成,退出!     break;
  5.   }//每次暫停一段時間,釋放表讓其他程序/執行緒訪問。usleep(50000)
  6. }

二、資料庫訪問效能優化

特別說明:

1、  本文只是面對資料庫應用開發的程式設計師,不適合專業DBA,DBA在資料庫效能優化方面需要了解更多的知識;

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的效能優化都應該按這個規則由上到下來診斷問題並提出解決方案,而不應該首先想到的是增加資源解決問題。

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

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

1、  減少資料訪問(減少磁碟訪問)

2、  返回更少資料(減少網路傳輸或磁碟訪問)

3、  減少互動次數(減少網路傳輸)

4、  減少伺服器CPU開銷(減少CPU及記憶體開銷)

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

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

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

優化法則

效能提升效果

優化成本

減少資料訪問

1~1000

返回更少資料

1~100

減少互動次數

1~20

減少伺服器CPU開銷

1~5

利用更多資源

@~10

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

二、oracle資料庫兩個基本概念

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

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

三、資料庫訪問優化法則詳解

減少資料訪問

建立並使用正確的索引

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

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

索引常見問題:

索引有哪些種類?

常見的索引有B-TREE索引、點陣圖索引、全文索引,點陣圖索引一般用於資料倉庫應用,全文索引由於使用較少,這裡不深入介紹。B-TREE索引包括很多擴充套件型別,如組合索引、反向索引、函式索引等等,以下是B-TREE索引的簡單介紹:

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

葉子節點內容:索引欄位內容+表記錄ROWID

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

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

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

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

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

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

SQL什麼條件會使用索引?

當欄位上建有索引時,通常以下情況會使用索引:

  1. INDEX_COLUMN = ?
  2. INDEX_COLUMN > ?
  3. INDEX_COLUMN >= ?
  4. INDEX_COLUMN < ?
  5. INDEX_COLUMN <= ?
  6. INDEX_COLUMN between ? and ?
  7. INDEX_COLUMN in (?,?,...,?)
  8. INDEX_COLUMN like ?||'%'(後導模糊查詢)
  9. 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效能。

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

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

2、返回更少的資料

2.1、資料分頁處理

一般資料分頁方式有:

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

將資料從應用伺服器全部下載到本地應用程式或瀏覽器,在應用程式或瀏覽器內部通過原生代碼進行分頁處理

優點:編碼簡單,減少客戶端與應用伺服器網路互動次數

缺點:首次互動時間長,佔用客戶端記憶體

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

2.1.2、應用伺服器分頁

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

  1. List list=executeQuery(“select * from employee order by id”);
  2. Int count= list.size();
  3. List subList= list.subList(10, 20);

優點:編碼簡單,只需要一次SQL互動,總資料與分頁資料差不多時效能較好。

缺點:總資料量較多時效能較差。

適應場景:資料庫系統不支援分頁處理,資料量較小並且可控。

2.1.3、資料庫SQL分頁

採用資料庫SQL分頁需要兩次SQL完成

一個SQL計算總數量

一個SQL返回分頁後的資料

優點:效能好

缺點:編碼複雜,各種資料庫語法不同,需要兩次SQL互動。

oracle資料庫一般採用rownum來進行分頁,常用分頁語法有如下兩種:

直接通過rownum分頁:

  1. select * from (
  2. select a.*,rownum rn from
  3. (select * from product a where company_id=? order by status) a
  4. where rownum<=20)
  5. where rn>10;

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

採用rowid分頁語法

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

  1. create index myindex on product(company_id,status);
  2. select b.* from (
  3. select * from (
  4. select a.*,rownum rn from
  5. (select rowid rid,status from product a where company_id=? order by status) a
  6. where rownum<=20)
  7. where rn>10) a, product b
  8. where a.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、只返回需要的欄位

通過去除不必要的返回欄位可以提高效能,例:

  1. 調整前:select * from product where company_id=?;
  2. 調整後: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)

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

  1. T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
  2. 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訪問。以上僅是理論計算值,實際情況需要根據具體環境測量。

3.2、In List

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

  1. for :var in ids[] do begin
  2. select * from mytable where id=:var;
  3. end;

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

select * from mytable where id in(: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引數的影響:

  1. String vsql ="select * from t_employee";
  2. PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
  3. pstmt.setFetchSize(1000);
  4. ResultSet rs = pstmt.executeQuery(vsql);
  5. int cnt = rs.getMetaData().getColumnCount();
  6. Object o;
  7. while (rs.next()) {
  8. for (int i = 1; i <= cnt; i++) {
  9. o = rs.getObject(i);
  10. }
  11. }

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

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

fetchsize

 elapse_time(s)

1

20.516

2

11.34

4

6.894