數據庫訪問優化法則

1.數據庫訪問優化法則

 

要正確的優化SQL,咱們須要快速定位能性的瓶頸點,也就是說快速找到咱們SQL主要的開銷在哪裏?而大多數狀況性能最慢的設備會是瓶頸點,以下載時網絡速度可能會是瓶頸點,本地複製文件時硬盤可能會是瓶頸點,爲何這些通常的工做咱們能快速確認瓶頸點呢,由於咱們對這些慢速設備的性能數據有一些基本的認識,如網絡帶寬是2Mbps,硬盤是每分鐘7200轉等等。所以,爲了快速找到SQL的性能瓶頸點,咱們也須要了解咱們計算機系統的硬件基本性能指標,下圖展現的當前主流計算機性能指標數據。html

 

 

從圖上能夠看到基本上每種設備都有兩個指標:java

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

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

 

從上圖能夠看出,計算機系統硬件性能從高到代依次爲:程序員

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

因爲SSD硬盤還處於快速發展階段,因此本文的內容不涉及SSD相關應用系統。算法

根據數據庫知識,咱們能夠列出每種硬件主要的工做內容:sql

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

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

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

 

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

 

 

這個優化法則概括爲5個層次:

一、  減小數據訪問(減小磁盤訪問)

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

三、  減小交互次數(減小網絡傳輸)

四、  減小服務器CPU開銷(減小CPU及內存開銷)

五、  利用更多資源(增長資源)

 

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

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

 

優化法則

性能提高效果

優化成本

減小數據訪問

1~1000

返回更少數據

1~100

減小交互次數

1~20

減小服務器CPU開銷

1~5

利用更多資源

@~10

 

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

 

2、Oracle數據庫兩個基本概念

數據塊(Block)

數據塊是數據庫中數據在磁盤中存儲的最小單位,也是一次IO訪問的最小單位,一個數據塊一般能夠存儲多條記錄,數據塊大小是DBA在建立數據庫或表空間時指定,可指定爲2K、4K、8K、16K或32K字節。下圖是一個Oracle數據庫典型的物理結構,一個數據庫能夠包括多個數據文件,一個數據文件內又包含多個數據塊;

 

 

ROWID

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

 

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

一、減小數據訪問

1.一、建立並使用正確的索引

數據庫索引的原理很是簡單,但在複雜的表中真正能正確使用索引的人不多,即便是專業的DBA也不必定能徹底作到最優。

索引會大大增長表記錄的DML(INSERT,UPDATE,DELETE)開銷,正確的索引可讓性能提高100,1000倍以上,不合理的索引也可能會讓性能降低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提升性能的條件比較特殊,使用很差反而性能會更差。

 

咱們通常在什麼字段上建索引?

這是一個很是複雜的話題,須要對業務及數據充分分析後再能得出結果。主鍵及外鍵一般都要有索引,其它須要建索引的字段應知足如下條件:

一、字段出如今查詢條件中,而且查詢條件能夠使用索引;

二、語句執行頻率高,一天會有幾千次以上;

三、經過字段條件可篩選的記錄集很小,那數據篩選比例是多少才適合?

這個沒有固定值,須要根據表數據量來評估,如下是經驗公式,可用於快速評估:

小表(記錄數小於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.二、只經過索引訪問數據

有些時候,咱們只是訪問表中的幾個字段,而且字段內容較少,咱們能夠爲這幾個字段單獨創建一個組合索引,這樣就能夠直接只經過訪問索引就能獲得數據,通常索引佔用的磁盤空間比表小不少,因此這種方式能夠大大減小磁盤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.三、優化SQL執行計劃

SQL執行計劃是關係型數據庫最核心的技術之一,它表示SQL執行時的數據訪問算法。因爲業務需求愈來愈複雜,表數據量也愈來愈大,程序員愈來愈懶惰,SQL也須要支持很是複雜的業務邏輯,但SQL的性能還須要提升,所以,優秀的關係型數據庫除了須要支持複雜的SQL語法及更多函數外,還須要有一套優秀的算法庫來提升SQL性能。

目前ORACLE有SQL執行計劃的算法約300種,並且一直在增長,因此SQL執行計劃是一個很是複雜的課題,一個普通DBA能掌握50種就很不錯了,就算是資深DBA也不可能把每一個執行計劃的算法描述清楚。雖然有這麼多種算法,但並不表示咱們沒法優化執行計劃,由於咱們經常使用的SQL執行計劃算法也就十幾個,若是一個程序員能把這十幾個算法搞清楚,那就掌握了80%的SQL執行計劃調優知識。

因爲篇幅的緣由,SQL執行計劃須要專題介紹,在這裏就很少說了。

 

二、返回更少的數據

2.一、數據分頁處理

通常數據分頁方式有:

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

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

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

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

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

2.1.二、應用服務器分頁

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

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

Int count= list.size();

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

 

優勢:編碼簡單,只須要一次SQL交互,總數據與分頁數據差很少時性能較好。

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

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

 

2.1.三、數據庫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個產品,假設訪問公司索引須要50個IO,2條記錄須要1個表數據IO。

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

 

2.二、只返回須要的字段

經過去除沒必要要的返回字段能夠提升性能,例:

調整前:select * from product where company_id=?;

調整後:select id,name from product where company_id=?;

 

優勢:

一、減小數據在網絡上傳輸開銷

二、減小服務器數據處理開銷

三、減小客戶端內存佔用

四、字段變動時提早發現問題,減小程序BUG

五、若是訪問的全部字段恰好在一個索引裏面,則能夠使用純索引訪問提升性能。

缺點:增長編碼工做量

因爲會增長一些編碼工做量,因此通常需求經過開發規範來要求程序員這麼作,不然等項目上線後再整改工做量更大。

若是你的查詢表中有大字段或內容較多的字段,如備註信息、文件內容等等,那在查詢表時必定要注意這方面的問題,不然可能會帶來嚴重的性能問題。若是表常常要查詢而且請求大內容字段的機率很低,咱們能夠採用分表處理,將一個大表分拆成兩個一對一的關係表,將不經常使用的大內容字段放在一張單獨的表中。如一張存儲上傳文件的表:

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.一、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.二、In 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請求的數量,從而提升性能。那若是有10000個ID,那是否是所有放在一條SQL裏處理呢?答案確定是否認的。首先大部份數據庫都會有SQL長度和IN裏個數的限制,如ORACLE的IN裏就不容許超過1000個值

另外當前數據庫通常都是採用基於成本的優化規則,當IN數量達到必定值時有可能改變SQL執行計劃,從索引訪問變成全表訪問,這將使性能急劇變化。隨着SQL中IN的裏面的值個數增長,SQL的執行計劃會更復雜,佔用的內存將會變大,這將會增長服務器CPU及內存成本。

評估在IN裏面一次放多少個值還須要考慮應用服務器本地內存的開銷,有併發訪問時要計算本地數據使用週期內的併發上限,不然可能會致使內存溢出。

綜合考慮,通常IN裏面的值個數超過20個之後性能基本沒什麼太大變化,也特別說明不要超過100,超事後可能會引發執行計劃的不穩定性及增長數據庫CPU及內存成本,這個須要專業DBA評估。

 

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

注:圖中fetchsize在128之後會有一些小的波動,這並非測試偏差,而是因爲resultset填充到具體對像時間不一樣的緣由,因爲resultset已經到本地內存裏了,因此估計是因爲CPU的L1,L2 Cache命中率變化形成,因爲變化不大,因此筆者也未深刻分析緣由。

 

iBatis的SqlMapping配置文件能夠對每一個SQL語句指定fetchsize大小,以下所示:

 

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

select * from employee

</select>

 

3.四、使用存儲過程

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

a:將A表數據所有取出到客戶端;

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

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

 

若是採用存儲過程你能夠將整個業務邏輯封裝在存儲過程裏,而後在客戶端直接調用存儲過程處理,這樣能夠減小網絡交互的成本。

固然,存儲過程也並非十全十美,存儲過程有如下缺點:

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

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

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

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

e、若是存儲過程當中有複雜運算的話,會增長一些數據庫服務端的處理成本,對於集中式數據庫可能會致使系統可擴展性問題。

f、爲了提升性能,數據庫會把存儲過程代碼編譯成中間運行代碼(相似於java的class文件),因此更像靜態語言。當存儲過程引用的對像(表、視圖等等)結構改變後,存儲過程須要從新編譯才能生效,在24*7高併發應用場景,通常都是在線變動結構的,因此在變動的瞬間要同時編譯存儲過程,這可能會致使數據庫瞬間壓力上升引發故障(Oracle數據庫就存在這樣的問題)。

 

我的觀點:普通業務邏輯儘可能不要使用存儲過程,定時性的ETL任務或報表統計函數能夠根據團隊資源狀況採用存儲過程處理。

 

3.五、優化業務邏輯

要經過優化業務邏輯來提升性能是比較困難的,這須要程序員對所訪問的數據及業務流程很是清楚。

舉一個案例:

某移動公司推出優惠套參,活動對像爲VIP會員而且2010年1,2,3月平均話費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.六、使用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

    }

}

 

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

 

四、減小數據庫服務器CPU運算

4.一、使用綁定變量

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

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

綁定變量寫法:

Select * from employee where id=?

Preparestatement.setInt(1,1234567)

 

Java中Preparestatement就是爲處理綁定變量提供的對像,綁定變量有如下優勢:

一、防止SQL注入

二、提升SQL可讀性

三、提升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.二、合理使用排序

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.三、減小比較操做

咱們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.四、大量複雜運算在客戶端處理

什麼是複雜運算,通常我認爲是一秒鐘CPU只能作10萬次之內的運算。如含小數的對數及指數運算、三角函數、3DES及BASE64數據加密算法等等。

若是有大量這類函數運算,儘可能放在客戶端處理,通常CPU每秒中也只能處理1萬-10萬次這樣的函數運算,放在數據庫內不利於高併發處理。

 

五、利用更多的資源

5.一、客戶端多進程並行訪問

多進程並行訪問是指在客戶端建立多個進程(線程),每一個進程創建一個與數據庫的鏈接,而後同時向數據庫提交訪問請求。當數據庫主機資源有空閒時,咱們能夠採用客戶端多進程並行訪問的方法來提升性能。若是數據庫主機已經很忙時,採用多進程並行訪問性能不會提升,反而可能會更慢。因此使用這種方式最好與DBA或系統管理員進行溝通後再決定是否採用。

 

例如:

咱們有10000個產品ID,如今須要根據ID取出產品的詳細信息,若是單線程訪問,按每一個IO要5ms計算,忽略主機CPU運算及網絡傳輸時間,咱們須要50s才能完成任務。若是採用5個並行訪問,每一個進程訪問2000個ID,那麼10s就有可能完成任務。

那是否是並行數越多越好呢,開1000個並行是否只要50ms就搞定,答案確定是否認的,當並行數超過服務器主機資源的上限時性能就不會再提升,若是再增長反而會增長主機的進程間調度成本和進程衝突機率。

 

如下是一些如何設置並行數的基本建議:

若是瓶頸在服務器主機,可是主機還有空閒資源,那麼最大並行數取主機CPU核數和主機提供數據服務的磁盤數兩個參數中的最小值,同時要保證主機有資源作其它任務。

若是瓶頸在客戶端處理,可是客戶端還有空閒資源,那建議不要增長SQL的並行,而是用一個進程取回數據後在客戶端起多個進程處理便可,進程數根據客戶端CPU核數計算。

若是瓶頸在客戶端網絡,那建議作數據壓縮或者增長多個客戶端,採用map reduce的架構處理。

若是瓶頸在服務器網絡,那須要增長服務器的網絡帶寬或者在服務端將數據壓縮後再處理了。

 

5.二、數據庫並行處理

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

 

 

並非全部的SQL均可以使用並行處理,通常只有對錶或索引進行所有訪問時才能夠使用並行。數據庫表默認是不打開並行訪問,因此須要指定SQL並行的提示,以下所示:

select /*+parallel(a,4)*/ * from employee;

 

並行的優勢:

使用多進程處理,充分利用數據庫主機資源(CPU,IO),提升性能。

並行的缺點:

一、單個會話佔用大量資源,影響其它會話,因此只適合在主機負載低時期使用;

二、只能採用直接IO訪問,不能利用緩存數據,因此執行前會觸發將髒緩存數據寫入磁盤操做。

 

注:

一、並行處理在OLTP類系統中慎用,使用不當會致使一個會話把主機資源所有佔用,而正常事務得不到及時響應,因此通常只是用於數據倉庫平臺。

二、通常對於百萬級記錄如下的小表採用並行訪問性能並不能提升,反而可能會讓性能更差。

 

SQL性能優化  --- 面試題

今天面試,我簡歷上寫了熟悉sql的性能優化,可是今天面試,一時想不起別的,就僅僅說出了一條,在這裏再總結一些,完善本身的知識點。

我常常用的數據庫是oracle,因此個人sql優化是程序員針對於oracle的。

總結,這個sql優化是針對程序員的,而不是針對dba的,主要就是第一,儘可能防止模糊,明確指出,即用列名代替*,第二,在where語句上下工夫。第三多表查詢和子查詢,第四儘可能使用綁定。

 

數據庫性能優化之SQL語句優化1

1、問題的提出

在應用系統開發初期,因爲開發數據庫數據比較少,對於查詢SQL語句,複雜視圖的的編寫等體會不出SQL語句各類寫法的性能優劣,可是若是將應用系統提交實際應用後,隨着數據庫中數據的增長,系統的響應速度就成爲目前系統須要解決的最主要的問題之一。系統優化中一個很重要的方面就是SQL語句的優化。對於海量數據,劣質SQL語句和優質SQL語句之間的速度差異能夠達到上百倍,可見對於一個系統不是簡單地能實現其功能就可,而是要寫出高質量的SQL語句,提升系統的可用性。

在多數狀況下,Oracle使用索引來更快地遍歷表,優化器主要根據定義的索引來提升性能。可是,若是在SQL語句的where子句中寫的SQL代碼不合理,就會形成優化器刪去索引而使用全表掃描,通常就這種SQL語句就是所謂的劣質SQL語句。在編寫SQL語句時咱們應清楚優化器根據何種原則來刪除索引,這有助於寫出高性能的SQL語句。

2、SQL語句編寫注意問題

下面就某些SQL語句的where子句編寫中須要注意的問題做詳細介紹。在這些where子句中,即便某些列存在索引,可是因爲編寫了劣質的SQL,系統在運行該SQL語句時也不能使用該索引,而一樣使用全表掃描,這就形成了響應速度的極大下降。

1. 操做符優化

(a) IN 操做符

用IN寫出來的SQL的優勢是比較容易寫及清晰易懂,這比較適合現代軟件開發的風格。可是用IN的SQL性能老是比較低的,從Oracle執行的步驟來分析用IN的SQL與不用IN的SQL有如下區別:

ORACLE試圖將其轉換成多個表的鏈接,若是轉換不成功則先執行IN裏面的子查詢,再查詢外層的表記錄,若是轉換成功則直接採用多個表的鏈接方式查詢。因而可知用IN的SQL至少多了一個轉換的過程。通常的SQL均可以轉換成功,但對於含有分組統計等方面的SQL就不能轉換了。

推薦方案:在業務密集的SQL當中儘可能不採用IN操做符,用EXISTS 方案代替。

(b) NOT IN操做符

此操做是強列不推薦使用的,由於它不能應用表的索引。

推薦方案:用NOT EXISTS 方案代替

(c) IS NULL 或IS NOT NULL操做(判斷字段是否爲空)

判斷字段是否爲空通常是不會應用索引的,由於索引是不索引空值的。不能用null做索引,任何包含null值的列都將不會被包含在索引中。即便索引有多列這樣的狀況下,只要這些列中有一列含有null,該列就會從索引中排除。也就是說若是某列存在空值,即便對該列建索引也不會提升性能。任何在where子句中使用is null或is not null的語句優化器是不容許使用索引的。

推薦方案:用其它相同功能的操做運算代替,如:a is not null 改成 a>0 或a>’’等。不容許字段爲空,而用一個缺省值代替空值,如申請中狀態字段不容許爲空,缺省爲申請。

(d) > 及 < 操做符(大於或小於操做符)

大於或小於操做符通常狀況下是不用調整的,由於它有索引就會採用索引查找,但有的狀況下能夠對它進行優化,如一個表有100萬記錄,一個數值型字段A,30萬記錄的A=0,30萬記錄的A=1,39萬記錄的A=2,1萬記錄的A=3。那麼執行A>2與A>=3的效果就有很大的區別了,由於A>2時ORACLE會先找出爲2的記錄索引再進行比較,而A>=3時ORACLE則直接找到=3的記錄索引。

(e) LIKE操做符

LIKE操做符能夠應用通配符查詢,裏面的通配符組合可能達到幾乎是任意的查詢,可是若是用得很差則會產生性能上的問題,如LIKE ‘%5400%’ 這種查詢不會引用索引,而LIKE ‘X5400%’則會引用範圍索引。

一個實際例子:用YW_YHJBQK表中營業編號後面的戶標識號可來查詢營業編號 YY_BH LIKE ‘%5400%’ 這個條件會產生全表掃描,若是改爲YY_BH LIKE ’X5400%’ OR YY_BH LIKE ’B5400%’ 則會利用YY_BH的索引進行兩個範圍的查詢,性能確定大大提升。

帶通配符(%)的like語句:

一樣以上面的例子來看這種狀況。目前的需求是這樣的,要求在職工表中查詢名字中包含cliton的人。能夠採用以下的查詢SQL語句:

select * from employee where last_name like '%cliton%';

這裏因爲通配符(%)在搜尋詞首出現,因此Oracle系統不使用last_name的索引。在不少狀況下可能沒法避免這種狀況,可是必定要心中有底,通配符如此使用會下降查詢速度。然而當通配符出如今字符串其餘位置時,優化器就能利用索引。在下面的查詢中索引獲得了使用:

select * from employee where last_name like 'c%';

(f) UNION操做符

UNION在進行表連接後會篩選掉重複的記錄,因此在表連接後會對所產生的結果集進行排序運算,刪除重複的記錄再返回結果。實際大部分應用中是不會產生重複的記錄,最多見的是過程表與歷史表UNION。如: 
select * from gc_dfys 
union 
select * from ls_jg_dfys 
這個SQL在運行時先取出兩個表的結果,再用排序空間進行排序刪除重複的記錄,最後返回結果集,若是表數據量大的話可能會致使用磁盤進行排序。

推薦方案:採用UNION ALL操做符替代UNION,由於UNION ALL操做只是簡單的將兩個結果合併後就返回。

select * from gc_dfys 
union all 
select * from ls_jg_dfys

(g) 聯接列

對於有聯接的列,即便最後的聯接值爲一個靜態值,優化器是不會使用索引的。咱們一塊兒來看一個例子,假定有一個職工表(employee),對於一個職工的姓和名分紅兩列存放(FIRST_NAME和LAST_NAME),如今要查詢一個叫比爾.克林頓(Bill Cliton)的職工。

下面是一個採用聯接查詢的SQL語句:

select * from employss where first_name||''||last_name ='Beill Cliton';

上面這條語句徹底能夠查詢出是否有Bill Cliton這個員工,可是這裏須要注意,系統優化器對基於last_name建立的索引沒有使用。當採用下面這種SQL語句的編寫,Oracle系統就能夠採用基於last_name建立的索引。

*** where first_name ='Beill' and last_name ='Cliton';

(h) Order by語句

ORDER BY語句決定了Oracle如何將返回的查詢結果排序。Order by語句對要排序的列沒有什麼特別的限制,也能夠將函數加入列中(象聯接或者附加等)。任何在Order by語句的非索引項或者有計算表達式都將下降查詢速度。

仔細檢查order by語句以找出非索引項或者表達式,它們會下降性能。解決這個問題的辦法就是重寫order by語句以使用索引,也能夠爲所使用的列創建另一個索引,同時應絕對避免在order by子句中使用表達式。

(i) NOT

咱們在查詢時常常在where子句使用一些邏輯表達式,如大於、小於、等於以及不等於等等,也能夠使用and(與)、or(或)以及not(非)。NOT可用來對任何邏輯運算符號取反。下面是一個NOT子句的例子:

... where not (status ='VALID')

若是要使用NOT,則應在取反的短語前面加上括號,並在短語前面加上NOT運算符。NOT運算符包含在另一個邏輯運算符中,這就是不等於(<>)運算符。換句話說,即便不在查詢where子句中顯式地加入NOT詞,NOT仍在運算符中,見下例:

... where status <>'INVALID';

對這個查詢,能夠改寫爲不使用NOT:

select * from employee where salary<3000 or salary>3000;

雖然這兩種查詢的結果同樣,可是第二種查詢方案會比第一種查詢方案更快些。第二種查詢容許Oracle對salary列使用索引,而第一種查詢則不能使用索引。

2. SQL書寫的影響

(a) 同一功能同一性能不一樣寫法SQL的影響。

如一個SQL在A程序員寫的爲  Select * from zl_yhjbqk

B程序員寫的爲 Select * from dlyx.zl_yhjbqk(帶表全部者的前綴)

C程序員寫的爲 Select * from DLYX.ZLYHJBQK(大寫表名)

D程序員寫的爲 Select *  from DLYX.ZLYHJBQK(中間多了空格)

以上四個SQL在ORACLE分析整理以後產生的結果及執行的時間是同樣的,可是從ORACLE共享內存SGA的原理,能夠得出ORACLE對每一個SQL 都會對其進行一次分析,而且佔用共享內存,若是將SQL的字符串及格式寫得徹底相同,則ORACLE只會分析一次,共享內存也只會留下一次的分析結果,這不只能夠減小分析SQL的時間,並且能夠減小共享內存重複的信息,ORACLE也能夠準確統計SQL的執行頻率。

(b) WHERE後面的條件順序影響

WHERE子句後面的條件順序對大數據量表的查詢會產生直接的影響。如: 
Select * from zl_yhjbqk where dy_dj = '1KV如下' and xh_bz=1 
Select * from zl_yhjbqk where xh_bz=1 and dy_dj = '1KV如下' 
以上兩個SQL中dy_dj(電壓等級)及xh_bz(銷戶標誌)兩個字段都沒進行索引,因此執行的時候都是全表掃描,第一條SQL的dy_dj = '1KV如下'條件在記錄集內比率爲99%,而xh_bz=1的比率只爲0.5%,在進行第一條SQL的時候99%條記錄都進行dy_dj及xh_bz的比較,而在進行第二條SQL的時候0.5%條記錄都進行dy_dj及xh_bz的比較,以此能夠得出第二條SQL的CPU佔用率明顯比第一條低。

(c) 查詢表順序的影響

在FROM後面的表中的列表順序會對SQL執行性能影響,在沒有索引及ORACLE沒有對錶進行統計分析的狀況下,ORACLE會按表出現的順序進行連接,因而可知表的順序不對時會產生十分耗服物器資源的數據交叉。(注:若是對錶進行了統計分析,ORACLE會自動先進小表的連接,再進行大表的連接)

3. SQL語句索引的利用

(a) 對條件字段的一些優化

採用函數處理的字段不能利用索引,如:

substr(hbs_bh,1,4)=’5400’,優化處理:hbs_bh like ‘5400%’

trunc(sk_rq)=trunc(sysdate), 優化處理:sk_rq>=trunc(sysdate) and sk_rq<trunc(sysdate+1)

進行了顯式或隱式的運算的字段不能進行索引,如:ss_df+20>50,優化處理:ss_df>30

‘X’ || hbs_bh>’X5400021452’,優化處理:hbs_bh>’5400021542’

sk_rq+5=sysdate,優化處理:sk_rq=sysdate-5

hbs_bh=5401002554,優化處理:hbs_bh=’ 5401002554’,注:此條件對hbs_bh 進行隱式的to_number轉換,由於hbs_bh字段是字符型。

條件內包括了多個本表的字段運算時不能進行索引,如:

ys_df>cx_df,沒法進行優化 
qc_bh || kh_bh=’5400250000’,優化處理:qc_bh=’5400’ and kh_bh=’250000’

4. 更多方面SQL優化資料分享

(1) 選擇最有效率的表名順序(只在基於規則的優化器中有效):

ORACLE 的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最早處理,在FROM子句中包含多個表的狀況下,你必須選擇記錄條數最少的表做爲基礎表。若是有3個以上的錶鏈接查詢, 那就須要選擇交叉表(intersection table)做爲基礎表, 交叉表是指那個被其餘表所引用的表.

(2) WHERE子句中的鏈接順序:

ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的鏈接必須寫在其餘WHERE條件以前, 那些能夠過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.

(3) SELECT子句中避免使用 ‘ * ‘:

ORACLE在解析的過程當中, 會將'*' 依次轉換成全部的列名, 這個工做是經過查詢數據字典完成的, 這意味着將耗費更多的時間。

(4) 減小訪問數據庫的次數:

ORACLE在內部執行了許多工做: 解析SQL語句, 估算索引的利用率, 綁定變量 , 讀數據塊等。

(5) 在SQL*Plus , SQL*Forms和Pro*C中從新設置ARRAYSIZE參數, 能夠增長每次數據庫訪問的檢索數據量 ,建議值爲200。

(6) 使用DECODE函數來減小處理時間:

使用DECODE函數能夠避免重複掃描相同記錄或重複鏈接相同的表.

(7) 整合簡單,無關聯的數據庫訪問:

若是你有幾個簡單的數據庫查詢語句,你能夠把它們整合到一個查詢中(即便它們之間沒有關係) 。

(8) 刪除重複記錄:

最高效的刪除重複記錄方法 ( 由於使用了ROWID)例子: 
DELETE  FROM  EMP E  WHERE  E.ROWID > (SELECT MIN(X.ROWID) FROM  EMP X  WHERE  X.EMP_NO = E.EMP_NO)。

(9) 用TRUNCATE替代DELETE:

當刪除表中的記錄時,在一般狀況下, 回滾段(rollback segments ) 用來存放能夠被恢復的信息. 若是你沒有COMMIT事務,ORACLE會將數據恢復到刪除以前的狀態(準確地說是恢復到執行刪除命令以前的情況) 而當運用TRUNCATE時, 回滾段再也不存聽任何可被恢復的信息.當命令運行後,數據不能被恢復.所以不多的資源被調用,執行時間也會很短. (譯者按: TRUNCATE只在刪除全表適用,TRUNCATE是DDL不是DML) 。

(10) 儘可能多使用COMMIT:

只要有可能,在程序中儘可能多使用COMMIT, 這樣程序的性能獲得提升,需求也會由於COMMIT所釋放的資源而減小,COMMIT所釋放的資源: 
a. 回滾段上用於恢復數據的信息. 
b. 被程序語句得到的鎖 
c. redo log buffer 中的空間 
d. ORACLE爲管理上述3種資源中的內部花費

(11) 用Where子句替換HAVING子句:

避免使用HAVING子句, HAVING 只會在檢索出全部記錄以後纔對結果集進行過濾. 這個處理須要排序,總計等操做. 若是能經過WHERE子句限制記錄的數目,那就能減小這方面的開銷. (非oracle中)on、where、having這三個均可以加條件的子句中,on是最早執行,where次之,having最後,由於on是先把不符合條件的記錄過濾後才進行統計,它就能夠減小中間運算要處理的數據,按理說應該速度是最快的,where也應該比having快點的,由於它過濾數據後才進行sum,在兩個表聯接時才用on的,因此在一個表的時候,就剩下where跟having比較了。在這單表查詢統計的狀況下,若是要過濾的條件沒有涉及到要計算字段,那它們的結果是同樣的,只是where能夠使用rushmore技術,而having就不能,在速度上後者要慢若是要涉及到計算的字 段,就表示在沒計算以前,這個字段的值是不肯定的,根據上篇寫的工做流程,where的做用時間是在計算以前就完成的,而having就是在計算後才起做 用的,因此在這種狀況下,二者的結果會不一樣。在多表聯接查詢時,on比where更早起做用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表 後,再由where進行過濾,而後再計算,計算完後再由having進行過濾。因而可知,要想過濾條件起到正確的做用,首先要明白這個條件應該在何時起做用,而後再決定放在那裏。

(12) 減小對錶的查詢:

在含有子查詢的SQL語句中,要特別注意減小對錶的查詢.例子: 
SELECT  TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT TAB_NAME,DB_VER FROM  TAB_COLUMNS  WHERE  VERSION = 604)

(13) 經過內部函數提升SQL效率:

複雜的SQL每每犧牲了執行效率. 可以掌握上面的運用函數解決問題的方法在實際工做中是很是有意義的。

(14) 使用表的別名(Alias):

當在SQL語句中鏈接多個表時, 請使用表的別名並把別名前綴於每一個Column上.這樣一來,就能夠減小解析的時間並減小那些由Column歧義引發的語法錯誤。

(15) 用EXISTS替代IN、用NOT EXISTS替代NOT IN:

在許多基於基礎表的查詢中,爲了知足一個條件,每每須要對另外一個表進行聯接.在這種狀況下, 使用EXISTS(或NOT EXISTS)一般將提升查詢的效率. 在子查詢中,NOT IN子句將執行一個內部的排序和合並. 不管在哪一種狀況下,NOT IN都是最低效的 (由於它對子查詢中的表執行了一個全表遍歷). 爲了不使用NOT IN ,咱們能夠把它改寫成外鏈接(Outer Joins)或NOT EXISTS。 
例子: 
(高效)SELECT * FROM  EMP (基礎表)  WHERE  EMPNO > 0  AND  EXISTS (SELECT ‘X'  FROM DEPT  WHERE  DEPT.DEPTNO = EMP.DEPTNO  AND  LOC = ‘MELB') 
(低效)SELECT  * FROM  EMP (基礎表)  WHERE  EMPNO > 0  AND  DEPTNO IN(SELECT DEPTNO  FROM  DEPT  WHERE  LOC = ‘MELB')

(16) 識別'低效執行'的SQL語句:

雖然目前各類關於SQL優化的圖形化工具層出不窮,可是寫出本身的SQL工具來解決問題始終是一個最好的方法: 
SELECT  EXECUTIONS , DISK_READS, BUFFER_GETS, 
ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio, 
ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run, 
SQL_TEXT 
FROM  V$SQLAREA 
WHERE  EXECUTIONS>0 
AND  BUFFER_GETS > 0 
AND  (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8 
ORDER BY  4 DESC;

(17) 用索引提升效率:

索引是表的一個概念部分,用來提升檢索數據的效率,ORACLE使用了一個複雜的自平衡B-tree結構. 一般,經過索引查詢數據比全表掃描要快. 當ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 一樣在聯結多個表時使用索引也能夠提升效率. 另外一個使用索引的好處是,它提供了主鍵(primary key)的惟一性驗證.。那些LONG或LONG RAW數據類型, 你能夠索引幾乎全部的列. 一般, 在大型表中使用索引特別有效. 固然,你也會發現, 在掃描小表時,使用索引一樣能提升效率. 雖然使用索引能獲得查詢效率的提升,可是咱們也必須注意到它的代價. 索引須要空間來存儲,也須要按期維護, 每當有記錄在表中增減或索引列被修改時, 索引自己也會被修改. 這意味着每條記錄的INSERT , DELETE , UPDATE將爲此多付出4 , 5 次的磁盤I/O . 由於索引須要額外的存儲空間和處理,那些沒必要要的索引反而會使查詢反應時間變慢.。按期的重構索引是有必要的: 
ALTER  INDEX <INDEXNAME> REBUILD <TABLESPACENAME>

(18) 用EXISTS替換DISTINCT:

當提交一個包含一對多表信息(好比部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT. 通常能夠考慮用EXIST替換, EXISTS 使查詢更爲迅速,由於RDBMS核心模塊將在子查詢的條件一旦知足後,馬上返回結果. 例子: 
(低效): 
SELECT  DISTINCT  DEPT_NO,DEPT_NAME  FROM  DEPT D , EMP E WHERE  D.DEPT_NO = E.DEPT_NO 
(高效): 
SELECT  DEPT_NO,DEPT_NAME  FROM  DEPT D  WHERE  EXISTS ( SELECT ‘X'  FROM  EMP E  WHERE E.DEPT_NO = D.DEPT_NO);

(19) sql語句用大寫的;由於oracle老是先解析sql語句,把小寫的字母轉換成大寫的再執行。

(20) 在java代碼中儘可能少用鏈接符「+」鏈接字符串!

(21) 避免在索引列上使用NOT,一般咱們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函數相同的影響. 當ORACLE」遇到」NOT,他就會中止使用索引轉而執行全表掃描。

(22) 避免在索引列上使用計算 
WHERE子句中,若是索引列是函數的一部分.優化器將不使用索引而使用全表掃描.舉例: 
低效: 
SELECT … FROM  DEPT  WHERE SAL * 12 > 25000; 
高效: 
SELECT … FROM DEPT WHERE SAL > 25000/12;

(23) 用>=替代> 
高效: 
SELECT * FROM  EMP  WHERE  DEPTNO >=4 
低效: 
SELECT * FROM EMP WHERE DEPTNO >3 
二者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄然後者將首先定位到DEPTNO=3的記錄而且向前掃描到第一個DEPT大於3的記錄。

(24) 用UNION替換OR (適用於索引列)

一般狀況下, 用UNION替換WHERE子句中的OR將會起到較好的效果. 對索引列使用OR將形成全表掃描. 注意, 以上規則只針對多個索引列有效. 若是有column沒有被索引, 查詢效率可能會由於你沒有選擇OR而下降. 在下面的例子中, LOC_ID 和REGION上都建有索引. 
高效: 
SELECT LOC_ID , LOC_DESC , REGION 
FROM LOCATION 
WHERE LOC_ID = 10 
UNION 
SELECT LOC_ID , LOC_DESC , REGION 
FROM LOCATION 
WHERE REGION = 「MELBOURNE」 
低效: 
SELECT LOC_ID , LOC_DESC , REGION 
FROM LOCATION 
WHERE LOC_ID = 10 OR REGION = 「MELBOURNE」 
若是你堅持要用OR, 那就須要返回記錄最少的索引列寫在最前面.

(25) 用IN來替換OR

這是一條簡單易記的規則,可是實際的執行效果還須檢驗,在ORACLE8i下,二者的執行路徑彷佛是相同的. 
低效: 
SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30 
高效 
SELECT… FROM LOCATION WHERE LOC_IN  IN (10,20,30);

(26) 避免在索引列上使用IS NULL和IS NOT NULL

避免在索引中使用任何能夠爲空的列,ORACLE將沒法使用該索引.對於單列索引,若是列包含空值,索引中將不存在此記錄. 對於複合索引,若是每一個列都爲空,索引中一樣不存在此記錄. 若是至少有一個列不爲空,則記錄存在於索引中.舉例: 若是惟一性索引創建在表的A列和B列上, 而且表中存在一條記錄的A,B值爲(123,null) , ORACLE將不接受下一條具備相同A,B值(123,null)的記錄(插入). 然而若是全部的索引列都爲空,ORACLE將認爲整個鍵值爲空而空不等於空. 所以你能夠插入1000 條具備相同鍵值的記錄,固然它們都是空! 由於空值不存在於索引列中,因此WHERE子句中對索引列進行空值比較將使ORACLE停用該索引. 
低效: (索引失效) 
SELECT … FROM  DEPARTMENT  WHERE  DEPT_CODE IS NOT NULL; 
高效: (索引有效) 
SELECT … FROM  DEPARTMENT  WHERE  DEPT_CODE >=0;

(27) 老是使用索引的第一個列:

若是索引是創建在多個列上, 只有在它的第一個列(leading column)被where子句引用時,優化器纔會選擇使用該索引. 這也是一條簡單而重要的規則,當僅引用索引的第二個列時,優化器使用了全表掃描而忽略了索引。

(28) 用UNION-ALL 替換UNION ( 若是有可能的話):

當SQL 語句須要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併, 而後在輸出最終結果前進行排序. 若是用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會所以獲得提升. 須要注意的是,UNION ALL 將重複輸出兩個結果集合中相同記錄. 所以各位仍是要從業務需求分析使用UNION ALL的可行性. UNION 將對結果集合排序,這個操做會使用到SORT_AREA_SIZE這塊內存. 對於這塊內存的優化也是至關重要的. 下面的SQL能夠用來查詢排序的消耗量 
低效: 
SELECT  ACCT_NUM, BALANCE_AMT 
FROM  DEBIT_TRANSACTIONS 
WHERE TRAN_DATE = '31-DEC-95' 
UNION 
SELECT ACCT_NUM, BALANCE_AMT 
FROM DEBIT_TRANSACTIONS 
WHERE TRAN_DATE = '31-DEC-95' 
高效: 
SELECT ACCT_NUM, BALANCE_AMT 
FROM DEBIT_TRANSACTIONS 
WHERE TRAN_DATE = '31-DEC-95' 
UNION ALL 
SELECT ACCT_NUM, BALANCE_AMT 
FROM DEBIT_TRANSACTIONS 
WHERE TRAN_DATE = '31-DEC-95'

(29) 用WHERE替代ORDER BY:

ORDER BY 子句只在兩種嚴格的條件下使用索引. 
ORDER BY中全部的列必須包含在相同的索引中並保持在索引中的排列順序. 
ORDER BY中全部的列必須定義爲非空. 
WHERE子句使用的索引和ORDER BY子句中所使用的索引不能並列. 
例如: 
表DEPT包含如下列: 
DEPT_CODE PK NOT NULL 
DEPT_DESC NOT NULL 
DEPT_TYPE NULL 
低效: (索引不被使用) 
SELECT DEPT_CODE FROM  DEPT  ORDER BY  DEPT_TYPE 
高效: (使用索引) 
SELECT DEPT_CODE  FROM  DEPT  WHERE  DEPT_TYPE > 0

(30) 避免改變索引列的類型:

當比較不一樣數據類型的數據時, ORACLE自動對列進行簡單的類型轉換. 
假設 EMPNO是一個數值類型的索引列. 
SELECT …  FROM EMP  WHERE  EMPNO = ‘123' 
實際上,通過ORACLE類型轉換, 語句轉化爲: 
SELECT …  FROM EMP  WHERE  EMPNO = TO_NUMBER(‘123') 
幸運的是,類型轉換沒有發生在索引列上,索引的用途沒有被改變. 
如今,假設EMP_TYPE是一個字符類型的索引列. 
SELECT …  FROM EMP  WHERE EMP_TYPE = 123 
這個語句被ORACLE轉換爲: 
SELECT …  FROM EMP  WHERE TO_NUMBER(EMP_TYPE)=123 
由於內部發生的類型轉換, 這個索引將不會被用到! 爲了不ORACLE對你的SQL進行隱式的類型轉換, 最好把類型轉換用顯式表現出來. 注意當字符和數值比較時, ORACLE會優先轉換數值類型到字符類型。

分析select   emp_name   form   employee   where   salary   >   3000   在此語句中若salary是Float類型的,則優化器對其進行優化爲Convert(float,3000),由於3000是個整數,咱們應在編程時使用3000.0而不要等運行時讓DBMS進行轉化。一樣字符和整型數據的轉換。


(31) 須要小心的WHERE子句:

某些SELECT 語句中的WHERE子句不使用索引. 這裏有一些例子. 
在下面的例子裏, (1)‘!=' 將不使用索引. 記住, 索引只能告訴你什麼存在於表中, 而不能告訴你什麼不存在於表中. (2) ‘ ¦ ¦'是字符鏈接函數. 就象其餘函數那樣, 停用了索引. (3) ‘+'是數學函數. 就象其餘數學函數那樣, 停用了索引. (4)相同的索引列不能互相比較,這將會啓用全表掃描.

(32) a. 若是檢索數據量超過30%的表中記錄數.使用索引將沒有顯著的效率提升. b. 在特定狀況下, 使用索引也許會比全表掃描慢, 但這是同一個數量級上的區別. 而一般狀況下,使用索引比全表掃描要塊幾倍乃至幾千倍!

(33) 避免使用耗費資源的操做:

帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啓動SQL引擎執行耗費資源的排序(SORT)功能. DISTINCT須要一次排序操做, 而其餘的至少須要執行兩次排序. 一般, 帶有UNION, MINUS , INTERSECT的SQL語句均可以用其餘方式重寫. 若是你的數據庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是能夠考慮的, 畢竟它們的可讀性很強。

(34) 優化GROUP BY:

提升GROUP BY 語句的效率, 能夠經過將不須要的記錄在GROUP BY 以前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多. 
低效: 
SELECT JOB , AVG(SAL) 
FROM EMP 
GROUP by JOB 
HAVING JOB = ‘PRESIDENT' 
OR JOB = ‘MANAGER' 
高效: 
SELECT JOB , AVG(SAL) 
FROM EMP 
WHERE JOB = ‘PRESIDENT' 
OR JOB = ‘MANAGER' 
GROUP by JOB

 

轉自:http://my.oschina.net/xianggao/blog/87216

另有參考 http://my.oschina.net/xianggao/blog/87448 數據庫性能優化之SQL語句優化2

http://my.oschina.net/xianggao/blog/87450 數據庫性能優化之SQL語句優化3

http://my.oschina.net/xianggao/blog/87453 數據庫性能優化之SQL語句優化4

http://my.oschina.net/xianggao/blog/87223  關於如何造成一個好的數據庫設計

SQL性能優化二

 

  • 優化目標
    1. 減小 IO 次數
      IO永遠是數據庫最容易瓶頸的地方,這是由數據庫的職責所決定的,大部分數據庫操做中超過90%的時間都是 IO 操做所佔用的,減小 IO 次數是 SQL 優化中須要第一優先考慮,固然,也是收效最明顯的優化手段。
    2. 下降 CPU 計算
      除了 IO 瓶頸以外,SQL優化中須要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操做基本上都是 CPU 處理內存中的數據比較運算)。當咱們的 IO 優化作到必定階段以後,下降 CPU 計算也就成爲了咱們 SQL 優化的重要目標
  • 優化方法
    1. 改變 SQL 執行計劃
      明確了優化目標以後,咱們須要肯定達到咱們目標的方法。對於 SQL 語句來講,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘可能「少走彎路」,儘可能經過各類「捷徑」來找到咱們須要的數據,以達到 「減小 IO 次數」 和 「下降 CPU 計算」 的目標
  • 常見誤區
    1. count(1)和count(primary_key) 優於 count(*)
      不少人爲了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認爲這樣性能更好,其實這是一個誤區。對於有些場景,這樣作可能性能會更差,應爲數據庫對 count(*) 計數操做作了一些特別的優化。
    2. count(column) 和 count(*) 是同樣的
      這個誤區甚至在不少的資深工程師或者是 DBA 中都廣泛存在,不少人都會認爲這是理所固然的。實際上,count(column) 和 count(*) 是一個徹底不同的操做,所表明的意義也徹底不同。
      count(column) 是表示結果集中有多少個column字段不爲空的記錄
      count(*) 是表示整個結果集有多少條記錄
    3. select a,b from … 比 select a,b,c from … 可讓數據庫訪問更少的數據量
      這個誤區主要存在於大量的開發人員中,主要緣由是對數據庫的存儲原理不是太瞭解。
      實際上,大多數關係型數據庫都是按照行(row)的方式存儲,而數據存取操做都是以一個固定大小的IO單元(被稱做 block 或者 page)爲單位,通常爲4KB,8KB… 大多數時候,每一個IO單元中存儲了多行,每行都是存儲了該行的全部字段(lob等特殊類型字段除外)。
      因此,咱們是取一個字段仍是多個字段,實際上數據庫在表中須要訪問的數據量實際上是同樣的。
      固然,也有例外狀況,那就是咱們的這個查詢在索引中就能夠完成,也就是說當只取 a,b兩個字段的時候,不須要回表,而c這個字段不在使用的索引中,須要回表取得其數據。在這樣的狀況下,兩者的IO量會有較大差別。
    4. order by 必定須要排序操做
      咱們知道索引數據其實是有序的,若是咱們的須要的數據和某個索引的順序一致,並且咱們的查詢又經過這個索引來執行,那麼數據庫通常會省略排序操做,而直接將數據返回,由於數據庫知道數據已經知足咱們的排序需求了。
      實際上,利用索引來優化有排序需求的 SQL,是一個很是重要的優化手段
      延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更爲深刻的分析,尤爲是第一篇
    5. 執行計劃中有 filesort 就會進行磁盤文件排序
      有這個誤區其實並不能怪咱們,而是由於 MySQL 開發者在用詞方面的問題。filesort 是咱們在使用 explain 命令查看一條 SQL 的執行計劃的時候可能會看到在 「Extra」 一列顯示的信息。
      實際上,只要一條 SQL 語句須要進行排序操做,都會顯示「Using filesort」,這並不表示就會有文件排序操做。
      延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裏有更爲詳細的介紹
  • 基本原則
    1. 儘可能少 join
      MySQL 的優點在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,可是因爲其統計信息的量有限,優化器工做過程出現誤差的可能性也就更多。對於複雜的多表 Join,一方面因爲其優化器受限,再者在 Join 這方面所下的功夫還不夠,因此性能表現離 Oracle 等關係型數據庫前輩仍是有必定距離。但若是是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些數據庫前輩。
    2. 儘可能少排序
      排序操做會消耗較多的 CPU 資源,因此減小排序能夠在緩存命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
      對於MySQL來講,減小排序有多種辦法,好比:

       

      • 上面誤區中提到的經過利用索引來排序的方式進行優化
      • 減小參與排序的記錄條數
      • 非必要不對數據進行排序
    3. 儘可能避免 select *
      不少人看到這一點後以爲比較難理解,上面不是在誤區中剛剛說 select 子句中字段的多少並不會影響到讀取的數據嗎?
      是的,大多數時候並不會影響到 IO 量,可是當咱們還存在 order by 操做的時候,select 子句中的字段多少會在很大程度上影響到咱們的排序效率,這一點能夠經過我以前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較爲詳細的介紹。
      此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當咱們的查詢結果僅僅只須要在索引中就能找到的時候,仍是會極大減小 IO 量的。
    4. 儘可能用 join 代替子查詢
      雖然 Join 性能並不佳,可是和 MySQL 的子查詢比起來仍是有很是大的性能優點。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,可是到目前已經發布的全部穩定版本中都廣泛存在,一直沒有太大改善。雖然官方也在很早就認可這一問題,而且承諾儘快解決,可是至少到目前爲止咱們尚未看到哪個版本較好的解決了這一問題。
    5. 儘可能少 or
      當 where 子句中存在多個條件以「或」並存的時候,MySQL 的優化器並無很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,形成了其性能比較低下,不少時候使用 union all 或者是union(必要的時候)的方式來代替「or」會獲得更好的效果。
    6. 儘可能用 union all 代替 union
      union 和 union all 的差別主要是前者須要將兩個(或者多個)結果集合並後再進行惟一性過濾操做,這就會涉及到排序,增長大量的 CPU 運算,加大資源消耗及延遲。因此當咱們能夠確認不可能出現重複結果集或者不在意重複結果集的時候,儘可能使用 union all 而不是 union。
    7. 儘可能早過濾
      這一優化策略其實最多見於索引的優化設計中(將過濾性更好的字段放得更靠前)。
      在 SQL 編寫中一樣能夠使用這一原則來優化一些 Join 的 SQL。好比咱們在多個表進行分頁數據查詢的時候,咱們最好是可以在一個表上先過濾好數據分好頁,而後再用分好頁的結果集與另外的表 Join,這樣能夠儘量多的減小沒必要要的 IO 操做,大大節省 IO 操做所消耗的時間。
    8. 避免類型轉換
      這裏所說的「類型轉換」是指 where 子句中出現 column 字段的類型和傳入的參數類型不一致的時候發生的類型轉換:

       

      • 人爲在column_name 上經過轉換函數進行轉換
        直接致使 MySQL(實際上其餘數據庫也會有一樣的問題)沒法使用索引,若是非要轉換,應該在傳入的參數上進行轉換
      • 由數據庫本身進行轉換
        若是咱們傳入的數據類型和字段類型不一致,同時咱們又沒有作任何類型轉換處理,MySQL 可能會本身對咱們的數據進行類型轉換操做,也可能不進行處理而交由存儲引擎去處理,這樣一來,就會出現索引沒法使用的狀況而形成執行計劃問題。
    9. 優先優化高併發的 SQL,而不是執行頻率低某些「大」SQL
      對於破壞性來講,高併發的 SQL 老是會比低頻率的來得大,由於高併發的 SQL 一旦出現問題,甚至不會給咱們任何喘息的機會就會將系統壓跨。而對於一些雖然須要消耗大量 IO 並且響應很慢的 SQL,因爲頻率低,即便遇到,最多就是讓整個系統響應慢一點,但至少可能撐一下子,讓咱們有緩衝的機會。
    10. 從全局出發優化,而不是片面調整
      SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中全部的 SQL,尤爲是在經過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。
    11. 儘量對每一條運行在數據庫中的SQL進行 explain
      優化 SQL,須要作到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對數據庫中運行的 SQL 進行了一段時間的優化以後,很明顯的問題 SQL 可能已經不多了,大多都須要去發掘,這時候就須要進行大量的 explain 操做收集執行計劃,並判斷是否須要進行優化
     
     


    Mysql性能優化之引擎的選擇

     

    MySQL 的存儲引擎多是全部關係型數據庫產品中最具備特點的了,不只能夠同時使用多種存儲引擎,並且每種存儲引擎和MySQL之間使用插件方式這種很是鬆的耦合關係。

    因爲各存儲引擎功能特性差別較大,這篇文章主要是介紹如何來選擇合適的存儲引擎來應對不一樣的業務場景。

    • MyISAM
      • 特性
        1. 不支持事務:MyISAM存儲引擎不支持事務,因此對事務有要求的業務場景不能使用
        2. 表級鎖定:其鎖定機制是表級索引,這雖然可讓鎖定的實現成本很小可是也同時大大下降了其併發性能
        3. 讀寫互相阻塞:不只會在寫入的時候阻塞讀取,MyISAM還會在讀取的時候阻塞寫入,但讀自己並不會阻塞另外的讀
        4. 只會緩存索引:MyISAM能夠經過key_buffer緩存以大大提升訪問性能減小磁盤IO,可是這個緩存區只會緩存索引,而不會緩存數據
      • 適用場景
        1. 不須要事務支持(不支持)
        2. 併發相對較低(鎖定機制問題)
        3. 數據修改相對較少(阻塞問題)
        4. 以讀爲主
        5. 數據一致性要求不是很是高
      • 最佳實踐
        1. 儘可能索引(緩存機制)
        2. 調整讀寫優先級,根據實際需求確保重要操做更優先
        3. 啓用延遲插入改善大批量寫入性能
        4. 儘可能順序操做讓insert數據都寫入到尾部,減小阻塞
        5. 分解大的操做,下降單個操做的阻塞時間
        6. 下降併發數,某些高併發場景經過應用來進行排隊機制
        7. 對於相對靜態的數據,充分利用Query Cache能夠極大的提升訪問效率
        8. MyISAM的Count只有在全表掃描的時候特別高效,帶有其餘條件的count都須要進行實際的數據訪問
    • InnoDB
      • 特性
        1. 具備較好的事務支持:支持4個事務隔離級別,支持多版本讀
        2. 行級鎖定:經過索引實現,全表掃描仍然會是表鎖,注意間隙鎖的影響
        3. 讀寫阻塞與事務隔離級別相關
        4. 具備很是高效的緩存特性:能緩存索引,也能緩存數據
        5. 整個表和主鍵以Cluster方式存儲,組成一顆平衡樹
        6. 全部Secondary Index都會保存主鍵信息
      • 適用場景
        1. 須要事務支持(具備較好的事務特性)
        2. 行級鎖定對高併發有很好的適應能力,但須要確保查詢是經過索引完成
        3. 數據更新較爲頻繁的場景
        4. 數據一致性要求較高
        5. 硬件設備內存較大,能夠利用InnoDB較好的緩存能力來提升內存利用率,儘量減小磁盤 IO
      • 最佳實踐
        1. 主鍵儘量小,避免給Secondary index帶來過大的空間負擔
        2. 避免全表掃描,由於會使用表鎖
        3. 儘量緩存全部的索引和數據,提升響應速度
        4. 在大批量小插入的時候,儘可能本身控制事務而不要使用autocommit自動提交
        5. 合理設置innodb_flush_log_at_trx_commit參數值,不要過分追求安全性
        6. 避免主鍵更新,由於這會帶來大量的數據移動
    • NDBCluster
      • 特性
        1. 分佈式:分佈式存儲引擎,能夠由多個NDBCluster存儲引擎組成集羣分別存放總體數據的一部分
        2. 支持事務:和Innodb同樣,支持事務
        3. 可與mysqld不在一臺主機:能夠和mysqld分開存在於獨立的主機上,而後經過網絡和mysqld通訊交互
        4. 內存需求量巨大:新版本索引以及被索引的數據必須存放在內存中,老版本全部數據和索引必須存在與內存中
      • 適用場景
        1. 具備很是高的併發需求
        2. 對單個請求的響應並非很是的critical
        3. 查詢簡單,過濾條件較爲固定,每次請求數據量較少,又不但願本身進行水平Sharding
      • 最佳實踐
        1. 儘量讓查詢簡單,避免數據的跨節點傳輸
        2. 儘量知足SQL節點的計算性能,大一點的集羣SQL節點會明顯多餘Data節點
        3. 在各節點之間儘量使用萬兆網絡環境互聯,以減小數據在網絡層傳輸過程當中的延時
     

    Mysql性能優化 --- 包含SQL、表結構、索引和緩存

     
    • 優化目標
      1. 減小 IO 次數
        IO永遠是數據庫最容易瓶頸的地方,這是由數據庫的職責所決定的,大部分數據庫操做中超過90%的時間都是 IO 操做所佔用的,減小 IO 次數是 SQL 優化中須要第一優先考慮,固然,也是收效最明顯的優化手段。
      2. 下降 CPU 計算
        除了 IO 瓶頸以外,SQL優化中須要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操做基本上都是 CPU 處理內存中的數據比較運算)。當咱們的 IO 優化作到必定階段以後,下降 CPU 計算也就成爲了咱們 SQL 優化的重要目標
    • 優化方法
      1. 改變 SQL 執行計劃
        明確了優化目標以後,咱們須要肯定達到咱們目標的方法。對於 SQL 語句來講,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘可能「少走彎路」,儘可能經過各類「捷徑」來找到咱們須要的數據,以達到 「減小 IO 次數」 和 「下降 CPU 計算」 的目標
    • 常見誤區
      1. count(1)和count(primary_key) 優於 count(*)
        不少人爲了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認爲這樣性能更好,其實這是一個誤區。對於有些場景,這樣作可能性能會更差,應爲數據庫對 count(*) 計數操做作了一些特別的優化。
      2. count(column) 和 count(*) 是同樣的
        這個誤區甚至在不少的資深工程師或者是 DBA 中都廣泛存在,不少人都會認爲這是理所固然的。實際上,count(column) 和 count(*) 是一個徹底不同的操做,所表明的意義也徹底不同。
        count(column) 是表示結果集中有多少個column字段不爲空的記錄
        count(*) 是表示整個結果集有多少條記錄
      3. select a,b from … 比 select a,b,c from … 可讓數據庫訪問更少的數據量
        這個誤區主要存在於大量的開發人員中,主要緣由是對數據庫的存儲原理不是太瞭解。
        實際上,大多數關係型數據庫都是按照行(row)的方式存儲,而數據存取操做都是以一個固定大小的IO單元(被稱做 block 或者 page)爲單位,通常爲4KB,8KB… 大多數時候,每一個IO單元中存儲了多行,每行都是存儲了該行的全部字段(lob等特殊類型字段除外)。
        因此,咱們是取一個字段仍是多個字段,實際上數據庫在表中須要訪問的數據量實際上是同樣的。
        固然,也有例外狀況,那就是咱們的這個查詢在索引中就能夠完成,也就是說當只取 a,b兩個字段的時候,不須要回表,而c這個字段不在使用的索引中,須要回表取得其數據。在這樣的狀況下,兩者的IO量會有較大差別。
      4. order by 必定須要排序操做
        咱們知道索引數據其實是有序的,若是咱們的須要的數據和某個索引的順序一致,並且咱們的查詢又經過這個索引來執行,那麼數據庫通常會省略排序操做,而直接將數據返回,由於數據庫知道數據已經知足咱們的排序需求了。
        實際上,利用索引來優化有排序需求的 SQL,是一個很是重要的優化手段
        延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更爲深刻的分析,尤爲是第一篇
      5. 執行計劃中有 filesort 就會進行磁盤文件排序
        有這個誤區其實並不能怪咱們,而是由於 MySQL 開發者在用詞方面的問題。filesort 是咱們在使用 explain 命令查看一條 SQL 的執行計劃的時候可能會看到在 「Extra」 一列顯示的信息。
        實際上,只要一條 SQL 語句須要進行排序操做,都會顯示「Using filesort」,這並不表示就會有文件排序操做。
        延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裏有更爲詳細的介紹
    • 基本原則
      1. 儘可能少 join
        MySQL 的優點在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,可是因爲其統計信息的量有限,優化器工做過程出現誤差的可能性也就更多。對於複雜的多表 Join,一方面因爲其優化器受限,再者在 Join 這方面所下的功夫還不夠,因此性能表現離 Oracle 等關係型數據庫前輩仍是有必定距離。但若是是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些數據庫前輩。
      2. 儘可能少排序
        排序操做會消耗較多的 CPU 資源,因此減小排序能夠在緩存命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
        對於MySQL來講,減小排序有多種辦法,好比:
        • 上面誤區中提到的經過利用索引來排序的方式進行優化
        • 減小參與排序的記錄條數
        • 非必要不對數據進行排序
        • 避免使用耗費資源的操做,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啓動SQL引擎 執行,耗費資源的排序(SORT)功能. DISTINCT須要一次排序操做, 而其餘的至少須要執行兩次排序
      3. 儘可能避免 select *
        不少人看到這一點後以爲比較難理解,上面不是在誤區中剛剛說 select 子句中字段的多少並不會影響到讀取的數據嗎?
        是的,大多數時候並不會影響到 IO 量,可是當咱們還存在 order by 操做的時候,select 子句中的字段多少會在很大程度上影響到咱們的排序效率,這一點能夠經過我以前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較爲詳細的介紹。
        此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當咱們的查詢結果僅僅只須要在索引中就能找到的時候,仍是會極大減小 IO 量的。
      4. 儘可能用 join 代替子查詢
        雖然 Join 性能並不佳,可是和 MySQL 的子查詢比起來仍是有很是大的性能優點。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,可是到目前已經發布的全部穩定版本中都廣泛存在,一直沒有太大改善。雖然官方也在很早就認可這一問題,而且承諾儘快解決,可是至少到目前爲止咱們尚未看到哪個版本較好的解決了這一問題。
      5. 儘可能少 or
        當 where 子句中存在多個條件以「或」並存的時候,MySQL 的優化器並無很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,形成了其性能比較低下,不少時候使用 union all 或者是union(必要的時候)的方式來代替「or」會獲得更好的效果。
      6. 儘可能用 union all 代替 union
        union 和 union all 的差別主要是前者須要將兩個(或者多個)結果集合並後再進行惟一性過濾操做,這就會涉及到排序,增長大量的 CPU 運算,加大資源消耗及延遲。因此當咱們能夠確認不可能出現重複結果集或者不在意重複結果集的時候,儘可能使用 union all 而不是 union。
      7. 儘可能早過濾
        這一優化策略其實最多見於索引的優化設計中(將過濾性更好的字段放得更靠前)。
        在 SQL 編寫中一樣能夠使用這一原則來優化一些 Join 的 SQL。好比咱們在多個表進行分頁數據查詢的時候,咱們最好是可以在一個表上先過濾好數據分好頁,而後再用分好頁的結果集與另外的表 Join,這樣能夠儘量多的減小沒必要要的 IO 操做,大大節省 IO 操做所消耗的時間。
      8. 避免類型轉換
        這裏所說的「類型轉換」是指 where 子句中出現 column 字段的類型和傳入的參數類型不一致的時候發生的類型轉換:
        • 人爲在column_name 上經過轉換函數進行轉換
          直接致使 MySQL(實際上其餘數據庫也會有一樣的問題)沒法使用索引,若是非要轉換,應該在傳入的參數上進行轉換
        • SELECT emp.ename, emp.job FROM emp WHERE emp.empno = 7369;
          不要使用:SELECT emp.ename, emp.job FROM emp WHERE emp.empno = ‘7369
        • 由數據庫本身進行轉換
          若是咱們傳入的數據類型和字段類型不一致,同時咱們又沒有作任何類型轉換處理,MySQL 可能會本身對咱們的數據進行類型轉換操做,也可能不進行處理而交由存儲引擎去處理,這樣一來,就會出現索引沒法使用的狀況而形成執行計劃問題。
      9. 優先優化高併發的 SQL,而不是執行頻率低某些「大」SQL
        對於破壞性來講,高併發的 SQL 老是會比低頻率的來得大,由於高併發的 SQL 一旦出現問題,甚至不會給咱們任何喘息的機會就會將系統壓跨。而對於一些雖然須要消耗大量 IO 並且響應很慢的 SQL,因爲頻率低,即便遇到,最多就是讓整個系統響應慢一點,但至少可能撐一下子,讓咱們有緩衝的機會。
      10. 從全局出發優化,而不是片面調整
        SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中全部的 SQL,尤爲是在經過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。
      11. 儘量對每一條運行在數據庫中的SQL進行 explain
        優化 SQL,須要作到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對數據庫中運行的 SQL 進行了一段時間的優化以後,很明顯的問題 SQL 可能已經不多了,大多都須要去發掘,這時候就須要進行大量的 explain 操做收集執行計劃,並判斷是否須要進行優化。
     

    2、MySQL 數據庫性能優化之表結構

    不少人都將 數據庫設計範式 做爲數據庫表結構設計「聖經」,認爲只要按照這個範式需求設計,就能讓設計出來的表結構足夠優化,既能保證性能優異同時還能知足擴展性要求。卻不知,在N年前被奉爲「聖經」的數據庫設計3範式早就已經不徹底適用了。這裏我整理了一些比較常見的數據庫表結構設計方面的優化技巧,但願對你們有用。因爲MySQL數據庫是基於行(Row)存儲的數據庫,而數據庫操做 IO 的時候是以 page(block)的方式,也就是說,若是咱們每條記錄所佔用的空間量減少,就會使每一個page中可存放的數據行數增大,那麼每次 IO 可訪問的行數也就增多了。反過來講,處理相同行數的數據,須要訪問的 page 就會減小,也就是 IO 操做次數下降,直接提高性能。此外,因爲咱們的內存是有限的,增長每一個page中存放的數據行數,就等於增長每一個內存塊的緩存數據量,同時還會提高內存換中數據命中的概率,也就是緩存命中率。
    • 數據類型選擇
      數據庫操做中最爲耗時的操做就是 IO 處理,大部分數據庫操做 90% 以上的時間都花在了 IO 讀寫上面。因此儘量減小 IO 讀寫量,能夠在很大程度上提升數據庫操做的性能。咱們沒法改變數據庫中須要存儲的數據,可是咱們能夠在這些數據的存儲方式方面花一些心思。下面的這些關於字段類型的優化建議主要適用於記錄條數較多,數據量較大的場景,由於精細化的數據類型設置可能帶來維護成本的提升,過分優化也可能會帶來其餘的問題:
      1. 數字類型:非萬不得已不要使用DOUBLE,不只僅只是存儲長度的問題,同時還會存在精確性的問題。一樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數存儲,能夠大大節省存儲空間,且不會帶來任何附加維護成本。對於整數的存儲,在數據量較大的狀況下,建議區分開 TINYINT / INT / BIGINT 的選擇,由於三者所佔用的存儲空間也有很大的差異,能肯定不會使用負數的字段,建議添加unsigned定義。固然,若是數據量較小的數據庫,也能夠不用嚴格區分三個整數類型。
      2. 字符類型:非萬不得已不要使用 TEXT 數據類型,其處理方式決定了他的性能要低於char或者是varchar類型的處理。定長字段,建議使用 CHAR 類型,不定長字段儘可能使用 VARCHAR,且僅僅設定適當的最大長度,而不是很是隨意的給一個很大的最大長度限定,由於不一樣的長度範圍,MySQL也會有不同的存儲處理。
      3. 時間類型:儘可能使用TIMESTAMP類型,由於其存儲空間只須要 DATETIME 類型的一半。對於只須要精確到某一天的數據類型,建議使用DATE類型,由於他的存儲空間只須要3個字節,比TIMESTAMP還少。不建議經過INT類型類存儲一個unix timestamp 的值,由於這太不直觀,會給維護帶來沒必要要的麻煩,同時還不會帶來任何好處。
      4. ENUM & SET:對於狀態字段,能夠嘗試使用 ENUM 來存放,由於能夠極大的下降存儲空間,並且即便須要增長新的類型,只要增長於末尾,修改結構也不須要重建表數據。若是是存放可預先定義的屬性數據呢?能夠嘗試使用SET類型,即便存在多種屬性,一樣能夠遊刃有餘,同時還能夠節省不小的存儲空間。
      5. LOB類型:強烈反對在數據庫中存放 LOB 類型數據,雖然數據庫提供了這樣的功能,但這不是他所擅長的,咱們更應該讓合適的工具作他擅長的事情,才能將其發揮到極致。在數據庫中存儲 LOB 數據就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 代碼同樣。
    • 字符編碼
      字符集直接決定了數據在MySQL中的存儲編碼方式,因爲一樣的內容使用不一樣字符集表示所佔用的空間大小會有較大的差別,因此經過使用合適的字符集,能夠幫助咱們儘量減小數據量,進而減小IO操做次數。
      1. 純拉丁字符能表示的內容,不必選擇 latin1 以外的其餘字符編碼,由於這會節省大量的存儲空間
      2. 若是咱們能夠肯定不須要存放多種語言,就不必非得使用UTF8或者其餘UNICODE字符類型,這回形成大量的存儲空間浪費
      3. MySQL的數據類型能夠精確到字段,因此當咱們須要大型數據庫中存放多字節數據的時候,能夠經過對不一樣表不一樣字段使用不一樣的數據類型來較大程度減少數據存儲量,進而下降 IO 操做次數並提升緩存命中率
    • 適當拆分
      有些時候,咱們可能會但願將一個完整的對象對應於一張數據庫表,這對於應用程序開發來講是頗有好的,可是有些時候可能會在性能上帶來較大的問題。當咱們的表中存在相似於 TEXT 或者是很大的 VARCHAR類型的大字段的時候,若是咱們大部分訪問這張表的時候都不須要這個字段,咱們就該義無反顧的將其拆分到另外的獨立表中,以減小經常使用數據所佔用的存儲空間。這樣作的一個明顯好處就是每一個數據塊中能夠存儲的數據條數能夠大大增長,既減小物理 IO 次數,也能大大提升內存中的緩存命中率。
    上面幾點的優化都是爲了減小每條記錄的存儲空間大小,讓每一個數據庫中可以存儲更多的記錄條數,以達到減小 IO 操做次數,提升緩存命中率。下面這個優化建議可能不少開發人員都會以爲不太理解,由於這是典型的反範式設計,並且也和上面的幾點優化建議的目標相違背。
    • 適度冗餘
      爲何咱們要冗餘?這不是增長了每條數據的大小,減小了每一個數據塊可存放記錄條數嗎?確實,這樣作是會增大每條記錄的大小,下降每條記錄中可存放數據的條數,可是在有些場景下咱們仍然仍是不得不這樣作:
      1. 被頻繁引用且只能經過 Join 2張(或者更多)大表的方式才能獲得的獨立小字段
        這樣的場景因爲每次Join僅僅只是爲了取得某個小字段的值,Join到的記錄又大,會形成大量沒必要要的 IO,徹底能夠經過空間換取時間的方式來優化。不過,冗餘的同時須要確保數據的一致性不會遭到破壞,確保更新的同時冗餘字段也被更新
    • 儘可能使用 NOT NULL
      NULL 類型比較特殊,SQL 難優化。雖然 MySQL NULL類型和 Oracle 的NULL 有差別,會進入索引中,但若是是一個組合索引,那麼這個NULL 類型的字段會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。
      不少人以爲 NULL 會節省一些空間,因此儘可能讓NULL來達到節省IO的目的,可是大部分時候這會拔苗助長,雖然空間上可能確實有必定節省,卻是帶來了不少其餘的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。因此儘可能確保 DEFAULT 值不是 NULL,也是一個很好的表結構設計優化習慣。

     3、MySQL 數據庫性能優化之索引優化

    你們都知道索引對於數據訪問的性能有很是關鍵的做用,都知道索引能夠提升數據訪問效率。爲何索引能提升數據訪問性能?他會不會有「反作用」?是否是索引建立越多,性能就越好?到底該如何設計索引,才能最大限度的發揮其效能?這篇文章主要是帶着上面這幾個問題來作一個簡要的分析,同時排除了業務場景所帶來的特殊性,請不要糾結業務場景的影響。
    • 索引爲何能提升數據訪問性能?
      不少人只知道索引可以提升數據庫的性能,但並非特別瞭解其原理,其實咱們能夠用一個生活中的示例來理解。咱們讓一位不太懂計算機的朋友去圖書館確認一本叫作《MySQL性能調優與架構設計》的書是否在藏,這樣對他說:「請幫我借一本計算機類的數據庫書籍,是屬於 MySQL 數據庫範疇的,叫作《MySQL性能調優與架構設計》」。朋友會根據所屬類別,前往存放「計算機」書籍區域的書架,而後再尋找「數據庫」類存放位置,再找到一堆講述「MySQL」的書籍,最後可能發現目標在藏(也可能已經借出不在書架上)。在這個過程當中: 「計算機」->「數據庫」->「MySQL」->「在藏」->《MySQL性能調優與架構設計》其實就是一個「根據索引查找數據」的典型案例,「計算機」->「數據庫」->「MySQL」->「在藏」 就是朋友查找書籍的索引。假設沒有這個索引,那查找這本書的過程會變成怎樣呢?朋友只能從圖書館入口一個書架一個書架的「遍歷」,直到找到《MySQL性能調優與架構設計》這本書爲止。若是幸運,可能在第一個書架就找到。但若是不幸呢,那就慘了,可能要將整個圖書館全部的書架都找一遍才能找到咱們想要的這本書。注:這個例子中的「索引」是記錄在朋友大腦中的,實際上,每一個圖書館都會有一個很是全的實際存在的索引系統(大多位於入口顯眼處),由不少個貼上了明顯標籤的小抽屜構成。這個索引系統中存放這很是齊全詳盡的索引數據,標識出咱們須要查找的「目標」在某個區域的某個書架上。並且每當有新的書籍入庫,舊的書籍銷燬以及書記信息修改,都須要對索引系統進行及時的修正。
    下面咱們經過上面這個生活中的小示例,來分析一下索引,看看能的出哪些結論?
    • 索引有哪些「反作用」?
      1. 圖書的變動(增,刪,改)都須要修訂索引,索引存在額外的維護成本
      2. 查找翻閱索引系統須要消耗時間,索引存在額外的訪問成本
      3. 這個索引系統須要一個地方來存放,索引存在額外的空間成本
    • 索引是否是越多越好?
      1. 若是咱們的這個圖書館只是一個進出中轉站,裏面的新書進來後很快就會轉發去其餘圖書館而從這個館藏中「清除」,那咱們的索引就只會不斷的修改,而不多會被用來查找圖書
        因此,對於相似於這樣的存在很是大更新量的數據,索引的維護成本會很是高,若是其檢索需求不多,並且對檢索效率並無很是高的要求的時候,咱們並不建議建立索引,或者是儘可能減小索引。
      2. 若是咱們的書籍量少到只有幾本或者就只有一個書架,索引並不會帶來什麼做用,甚至可能還會浪費一些查找索引所花費的時間。
        因此,對於數據量極小到經過索引檢索還不如直接遍從來得快的數據,也並不適合使用索引。
      3. 若是咱們的圖書館只有一個10平方的面積,如今連放書架都已經很是擁擠,並且館藏還在不斷增長,咱們還能考慮建立索引嗎?
        因此,當咱們連存儲基礎數據的空間都捉襟見肘的時候,咱們也應該儘可能減小低效或者是去除索引。
    • 索引該如何設計才高效?
      1. 若是咱們僅僅只是這樣告訴對方的:「幫我確認一本數據庫類別的講述 MySQL 的叫作《MySQL性能調優與架構設計》的書是否在藏」,結果又會如何呢?朋友只能一個大類區域一個大類區域的去尋找「數據庫」類別,而後再找到 「MySQL」範疇,再看到咱們所需是否在藏。因爲咱們少說了一個「計算機類」,朋友就必須到每個大類去尋找。
        因此,咱們應該儘可能讓查找條件儘量多的在索引中,儘量經過索引完成全部過濾,回表只是取出額外的數據字段。
      2. 若是咱們是這樣說的:「幫我確認一本講述 MySQL 的數據庫範疇的計算機叢書,叫作《MySQL性能調優與架構設計》,看是否在藏」。若是這位朋友並不知道計算機是一個大類,也不知道數據庫屬於計算機大類,那這位朋友就悲劇了。首先他得遍歷每一個類別確認「MySQL」存在於哪些類別中,而後從包含 「MySQL」 書籍中再看有哪些是「數據庫」範疇的(有可能部分是講述PHP或者其餘開發語言的),而後再排除非計算機類的(雖然可能並無必要),而後才能確認。
        因此,字段的順序對組合索引效率有相當重要的做用,過濾效果越好的字段須要更靠前。
      3. 若是咱們還有這樣一個需求(雖然基本不可能):「幫我將圖書館中全部的計算機圖書借來」。朋友若是經過索引來找,每次都到索引櫃找到計算機書籍所在的區域,而後從書架上搬下一格(假設只能以一格爲單位從書架上取下,類比數據庫中以block/page爲單位讀取),取出第一本,而後再從索引櫃找到計算機圖書所在區域,再搬下一格,取出一本… 如此往復直至取完全部的書。若是他不經過索引來找又會怎樣呢?他須要從地一個書架一直日後找,當找到計算機的書,搬下一格,取出全部計算機的書,再日後,直至全部書架所有看一遍。在這個過程當中,若是計算機類書籍較多,經過索引來取所花費的時間極可能要大於直接遍歷,由於不斷往復的索引翻閱所消耗的時間會很是長。(延伸閱讀:這裏有一篇之前寫的關於Oracle的文章,索引掃描仍是全表掃描(Index Scan Or Full Table Scan)
        因此,當咱們須要讀取的數據量佔整個數據量的比例較大抑或者說索引的過濾效果並非太好的時候,使用索引並不必定優於全表掃描。
      4. 若是咱們的朋友不知道「數據庫」這個類別能夠屬於「計算機」這個大類,抑或者圖書館的索引系統中這兩個類別屬性並無關聯關係,又會怎樣呢?也就是說,朋友獲得的是2個獨立的索引,一個是告知「計算機」這個大類所在的區域,一個是「數據庫」這個小類所在的區域(極可能是多個區域),那麼他只能兩者選其一來搜索個人需求。即便朋友能夠分別經過2個索引檢索而後本身在腦中取交集再找,那這樣的效率實際過程當中也會比較低下。
        因此,在實際使用過程當中,一次數據訪問通常只能利用到1個索引,這一點在索引建立過程當中必定要注意,不是說一條SQL語句中Where子句裏面每一個條件都有索引能對應上就能夠了。
      5. 最後總結一下法則:不要在創建的索引的數據列上進行下列操做:
        ◆避免對索引字段進行計算操做◆避免在索引字段上使用not,,!=◆避免在索引列上使用IS NULL和IS NOT NULL◆避免在索引列上出現數據類型轉換◆避免在索引字段上使用函數◆避免創建索引的列中使用空值。

     4、MySQL 數據庫性能優化之緩存參數優化

    數據庫屬於 IO 密集型的應用程序,其主要職責就是數據的管理及存儲工做。而咱們知道,從內存中讀取一個數據庫的時間是微秒級別,而從一塊普通硬盤上讀取一個IO是在毫秒級別,兩者相差3個數量級。因此,要優化數據庫,首先第一步須要優化的就是 IO,儘量將磁盤IO轉化爲內存IO。本文先從 MySQL 數據庫IO相關參數(緩存參數)的角度來看看能夠經過哪些參數進行IO優化:

    • query_cache_size/query_cache_type (global) Query cache 做用於整個 MySQL Instance,主要用來緩存 MySQL 中的 ResultSet,也就是一條SQL語句執行的結果集,因此僅僅只能針對select語句。當咱們打開了 Query Cache 功能,MySQL在接受到一條select語句的請求後,若是該語句知足Query Cache的要求(未顯式說明不容許使用Query Cache,或者已經顯式申明須要使用Query Cache),MySQL 會直接根據預先設定好的HASH算法將接受到的select語句以字符串方式進行hash,而後到Query Cache 中直接查找是否已經緩存。也就是說,若是已經在緩存中,該select請求就會直接將數據返回,從而省略了後面全部的步驟(如 SQL語句的解析,優化器優化以及向存儲引擎請求數據等),極大的提升性能。固然,Query Cache 也有一個致命的缺陷,那就是當某個表的數據有任何任何變化,都會致使全部引用了該表的select語句在Query Cache 中的緩存數據失效。因此,當咱們的數據變化很是頻繁的狀況下,使用Query Cache 可能會得不償失。Query Cache的使用須要多個參數配合,其中最爲關鍵的是 query_cache_size 和 query_cache_type ,前者設置用於緩存 ResultSet 的內存大小,後者設置在何場景下使用 Query Cache。在以往的經驗來看,若是不是用來緩存基本不變的數據的MySQL數據庫,query_cache_size 通常 256MB 是一個比較合適的大小。固然,這能夠經過計算Query Cache的命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))來進行調整。query_cache_type能夠設置爲0(OFF),1(ON)或者2(DEMOND),分別表示徹底不使用query cache,除顯式要求不使用query cache(使用sql_no_cache)以外的全部的select都使用query cache,只有顯示要求才使用query cache(使用sql_cache)。
    • binlog_cache_size (global) Binlog Cache 用於在打開了二進制日誌(binlog)記錄功能的環境,是 MySQL 用來提升binlog的記錄效率而設計的一個用於短期內臨時緩存binlog數據的內存區域。通常來講,若是咱們的數據庫中沒有什麼大事務,寫入也不是特別頻繁,2MB~4MB是一個合適的選擇。可是若是咱們的數據庫大事務較多,寫入量比較大,可與適當調高binlog_cache_size。同時,咱們能夠經過binlog_cache_use 以及 binlog_cache_disk_use來分析設置的binlog_cache_size是否足夠,是否有大量的binlog_cache因爲內存大小不夠而使用臨時文件(binlog_cache_disk_use)來緩存了。
    • key_buffer_size (global) Key Buffer 多是你們最爲熟悉的一個 MySQL 緩存參數了,尤爲是在 MySQL 沒有更換默認存儲引擎的時候,不少朋友可能會發現,默認的 MySQL 配置文件中設置最大的一個內存參數就是這個參數了。key_buffer_size 參數用來設置用於緩存 MyISAM存儲引擎中索引文件的內存區域大小。若是咱們有足夠的內存,這個緩存區域最好是可以存放下咱們全部的 MyISAM 引擎表的全部索引,以儘量提升性能。此外,當咱們在使用MyISAM 存儲的時候有一個及其重要的點須要注意,因爲 MyISAM 引擎的特性限制了他僅僅只會緩存索引塊到內存中,而不會緩存表數據庫塊。因此,咱們的 SQL 必定要儘量讓過濾條件都在索引中,以便讓緩存幫助咱們提升查詢效率。
    • bulk_insert_buffer_size (thread)和key_buffer_size同樣,這個參數一樣也僅做用於使用 MyISAM存儲引擎,用來緩存批量插入數據的時候臨時緩存寫入數據。當咱們使用以下幾種數據寫入語句的時候,會使用這個內存區域來緩存批量結構的數據以幫助批量寫入數據文件:insert … select …
      insert … values (…) ,(…),(…)…
      load data infile… into… (非空表)
    • innodb_buffer_pool_size(global)當咱們使用InnoDB存儲引擎的時候,innodb_buffer_pool_size 參數多是影響咱們性能的最爲關鍵的一個參數了,他用來設置用於緩存 InnoDB 索引及數據塊的內存區域大小,相似於 MyISAM 存儲引擎的 key_buffer_size 參數,固然,可能更像是 Oracle 的 db_cache_size。簡單來講,當咱們操做一個 InnoDB 表的時候,返回的全部數據或者去數據過程當中用到的任何一個索引塊,都會在這個內存區域中走一遭。和key_buffer_size 對於 MyISAM 引擎同樣,innodb_buffer_pool_size 設置了 InnoDB 存儲引擎需求最大的一塊內存區域的大小,直接關係到 InnoDB存儲引擎的性能,因此若是咱們有足夠的內存,儘可將該參數設置到足夠打,將盡量多的 InnoDB 的索引及數據都放入到該緩存區域中,直至所有。咱們能夠經過 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100% 計算緩存命中率,並根據命中率來調整 innodb_buffer_pool_size 參數大小進行優化。
    • innodb_additional_mem_pool_size(global)這個參數咱們平時調整的可能不是太多,不少人都使用了默認值,可能不少人都不是太熟悉這個參數的做用。innodb_additional_mem_pool_size 設置了InnoDB存儲引擎用來存放數據字典信息以及一些內部數據結構的內存空間大小,因此當咱們一個MySQL Instance中的數據庫對象很是多的時候,是須要適當調整該參數的大小以確保全部數據都能存放在內存中提升訪問效率的。這個參數大小是否足夠仍是比較容易知道的,由於當太小的時候,MySQL 會記錄 Warning 信息到數據庫的 error log 中,這時候你就知道該調整這個參數大小了。
    • innodb_log_buffer_size (global)這是 InnoDB 存儲引擎的事務日誌所使用的緩衝區。相似於 Binlog Buffer,InnoDB 在寫事務日誌的時候,爲了提升性能,也是先將信息寫入 Innofb Log Buffer 中,當知足 innodb_flush_log_trx_commit 參數所設置的相應條件(或者日誌緩衝區寫滿)以後,纔會將日誌寫到文件(或者同步到磁盤)中。能夠經過 innodb_log_buffer_size 參數設置其能夠使用的最大內存空間。
      注:innodb_flush_log_trx_commit 參數對 InnoDB Log 的寫入性能有很是關鍵的影響。該參數能夠設置爲0,1,2,解釋以下:0:log buffer中的數據將以每秒一次的頻率寫入到log file中,且同時會進行文件系統到磁盤的同步操做,可是每一個事務的commit並不會觸發任何log buffer 到log file的刷新或者文件系統到磁盤的刷新操做;
      1:在每次事務提交的時候將log buffer 中的數據都會寫入到log file,同時也會觸發文件系統到磁盤的同步;
      2:事務提交會觸發log buffer 到log file的刷新,但並不會觸發磁盤文件系統到磁盤的同步。此外,每秒會有一次文件系統到磁盤同步操做。此外,MySQL文檔中還提到,這幾種設置中的每秒同步一次的機制,可能並不會徹底確保很是準確的每秒就必定會發生同步,還取決於進程調度的問題。實際上,InnoDB 可否真正知足此參數所設置值表明的意義正常 Recovery 仍是受到了不一樣 OS 下文件系統以及磁盤自己的限制,可能有些時候在並無真正完成磁盤同步的狀況下也會告訴 mysqld 已經完成了磁盤同步。
    • innodb_max_dirty_pages_pct (global)這個參數和上面的各個參數不一樣,他不是用來設置用於緩存某種數據的內存大小的一個參數,而是用來控制在 InnoDB Buffer Pool 中能夠不用寫入數據文件中的Dirty Page 的比例(已經被修但尚未從內存中寫入到數據文件的髒數據)。這個比例值越大,從內存到磁盤的寫入操做就會相對減小,因此可以必定程度下減小寫入操做的磁盤IO。可是,若是這個比例值過大,當數據庫 Crash 以後重啓的時間可能就會很長,由於會有大量的事務數據須要從日誌文件恢復出來寫入數據文件中。同時,過大的比例值同時可能也會形成在達到比例設定上限後的 flush 操做「過猛」而致使性能波動很大。
    上面這幾個參數是 MySQL 中爲了減小磁盤物理IO而設計的主要參數,對 MySQL 的性能起到了相當重要的做用。
    —EOF—
    按照  mcsrainbow 朋友的要求,這裏列一下根據以往經驗獲得的相關參數的建議值:
    • query_cache_type : 若是所有使用innodb存儲引擎,建議爲0,若是使用MyISAM 存儲引擎,建議爲2,同時在SQL語句中顯式控制是不是喲你gquery cache
    • query_cache_size: 根據 命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))進行調整,通常不建議太大,256MB可能已經差很少了,大型的配置型靜態數據可適當調大
    • binlog_cache_size: 通常環境2MB~4MB是一個合適的選擇,事務較大且寫入頻繁的數據庫環境能夠適當調大,但不建議超過32MB
    • key_buffer_size: 若是不使用MyISAM存儲引擎,16MB足以,用來緩存一些系統表信息等。若是使用 MyISAM存儲引擎,在內存容許的狀況下,儘量將全部索引放入內存,簡單來講就是「越大越好」
    • bulk_insert_buffer_size: 若是常常性的須要使用批量插入的特殊語句(上面有說明)來插入數據,能夠適當調大該參數至16MB~32MB,不建議繼續增大,某人8MB
    • innodb_buffer_pool_size: 若是不使用InnoDB存儲引擎,能夠不用調整這個參數,若是須要使用,在內存容許的狀況下,儘量將全部的InnoDB數據文件存放如內存中,一樣將但來講也是「越大越好」
    • innodb_additional_mem_pool_size: 通常的數據庫建議調整到8MB~16MB,若是表特別多,能夠調整到32MB,能夠根據error log中的信息判斷是否須要增大
    • innodb_log_buffer_size: 默認是1MB,系的如頻繁的系統可適當增大至4MB~8MB。固然如上面介紹所說,這個參數實際上還和另外的flush參數相關。通常來講不建議超過32MB
    • innodb_max_dirty_pages_pct: 根據以往的經驗,重啓恢復的數據若是要超過1GB的話,啓動速度會比較慢,幾乎難以接受,因此建議不大於 1GB/innodb_buffer_pool_size(GB)*100 這個值。固然,若是你可以忍受啓動時間比較長,並且但願儘可能減小內存至磁盤的flush,能夠將這個值調整到90,但不建議超過90
    注:以上取值範圍僅僅只是個人根據以往遇到的數據庫場景所獲得的一些優化經驗值,並不必定適用於全部場景,因此在實際優化過程當中還須要你們本身不斷的調整分析,也歡迎你們隨時經過 Mail 與我聯繫溝通交流優化或者是架構方面的技術,一塊兒探討相互學習。
     
     

    Mysql優化總結

    1、索引
    一、建立索引:
    (1).ALTER TABLE   
     ALTER TABLE用來建立普通索引、UNIQUE索引或PRIMARY KEY索引。    
        
     ALTER TABLE table_name ADD INDEX index_name (column_list)   
      
     ALTER TABLE table_name ADD UNIQUE (column_list)   
      
     ALTER TABLE table_name ADD PRIMARY KEY (column_list)   
      
    (2)、CREATE INDEX   
     CREATE INDEX可對錶增長普通索引或UNIQUE索引。   
      
     CREATE INDEX index_name ON table_name (column_list)   
      
     CREATE UNIQUE INDEX index_name ON table_name (column_list)  
    二、查看索引  
      
     mysql> show index from tblname;   
      
     mysql> show keys from tblname; 
    三、刪除索引
     可利用ALTER TABLE或DROP INDEX語句來刪除索引。相似於CREATE INDEX語句,DROP INDEX能夠在ALTER TABLE 內部做爲一條語句處理,語法以下。  
     DROP INDEX index_name ON talbe_name   
      
     ALTER TABLE table_name DROP INDEX index_name   
      
     ALTER TABLE table_name DROP PRIMARY KEY   

    索引:http://www.cnblogs.com/hustcat/archive/2009/10/28/1591648.html
    **explain +select ·····用來獲取select語句的執行的相關信息及索引的使用等
    **describe table table_name;
    **analyze table table_name;查看錶的信息,幫助優化
    **show 查看執行狀態

    2、my.ini中的配置
    http://www.chinaz.com/program/2009/1210/100740.shtml
    mysql > show status; 能夠查看具體的設置 服務器的狀態
    具體的配置呀什麼,沒有親自試驗過

    3、數據表引擎
     一、MyISAM:mysql默認的
     二、InnoDB:支持事務、鎖、外鍵、聚簇索引
    引擎介紹:http://blog.csdn.net/cheungjustin/article/details/5999880
     http://limaolinjia.blog.163.com/blog/static/539162282011012145139/

    4、索引的類型:
     一、B-Tree索引
     二、hash索引
    具體的參考仍是一)

    5、事務
    數據表引擎使用InnoDB
    http://www.cnblogs.com/winner/archive/2011/11/09/2242272.html


    6、存儲過程
    經編譯和優化後存儲在數據庫服務器中,運行效率高,能夠下降客戶機和服務器之間的通訊量,有利於集中控制,易於維護 (P247)
    http://blog.sina.com.cn/s/blog_52d20fbf0100ofd5.html

    7、mysql profiling(mysql性能分析器)優化sql語句
    查看SQL執行消耗系統資源的信息
    ++++須要開啓+++
    具體使用:http://www.jiunile.com/mysql-profiling%E7%9A%84%E4%BD%BF%E7%94%A8.html

    8、慢查詢日誌++++須要開啓++++經過慢日誌查詢能夠知道哪些SQL語句執行效率低下,那些sql語句使用的頻率高等對MySQL查詢語句的監控、分析、優化是MySQL優化很是重要的一步。開啓慢查詢日誌後,因爲日誌記錄操做,在必定程度上會佔用CPU資源影響mysql的性能,可是能夠階段性開啓來定位性能瓶頸。

相關文章
相關標籤/搜索