聊聊數據庫優化

寫在前面的話

數據庫優化涉及到方方面面的知識,每種數據庫的架構,優化方式也都有着很大的差別,若是想作好數據庫優化要了解數據庫的技術架構、存儲結構、存儲方式、緩存結構、SQL語句執行過程等有很深入的瞭解。本文只是針對開發人員平常用到的通用的優化方法進行介紹,至於數據庫參數調整等數據庫相關內容也再也不本文的討論範圍以內。目的是讓開發人員對經常使用數據庫優化有所瞭解。mysql

數據庫優化的二八法則

二八法則認爲,在任何一組東西中,最重要的只佔其中一小部分,約20%,其他80%儘管是多數,倒是次要的,如:20%的富人擁有80%的財富。20%的權貴消耗了80%的資源,等等。數據庫的優化也符合二八法則:sql

  • 80%的性能問題是由20%的應用致使的。如少許大表的全表掃描致使的性能瓶頸。並非應用一有問題,都須要對系統進行重構,只需優化少部分存在性能問題的功能即可以使系統的性能大幅度提高。
  • 80%的性能問題能夠由20%的優化技術所解決。如增長索引,執行計劃分析等,能解決絕大部分性能問題

因此咱們若是可以瞭解常見的數據庫優化方法,能夠解決開發中遇到的80%問題。數據庫

數據庫優化該怎麼作

在小品裏面問大象放冰箱分幾步?把冰箱門打開,把大象放進去,把冰箱門關上。緩存

數據庫優化分幾步?找到具體影響數據庫性能的緣由,把問題解決了。bash

發現問題

誤區: 個人SQL語句執行的很快,問題確定不會出在我這裏。服務器

正解:SQL語句單次執行的快慢並不必定表明語句的好壞。有的語句雖然單次執行效率還比較高,可是隨着數據量的增長,併發量的增長極可能成爲性能瓶頸。架構

該怎麼作?SQL寫好了以後看一下這條SQL的執行計劃,用事實說話。併發

那麼什麼是執行計劃呢?oracle

SQL是一種傻瓜式語言,每個條件就是一個需求,訪問的順序不一樣就造成了不一樣的執行計劃。數據庫必須作出選擇,一次只能有一種訪問路徑。執行計劃是一條查詢語句在數據庫中的執行過程或訪問路徑的描述。函數

每種數據庫都有工具能夠查看SQL的執行計劃。只有執行計劃沒問題才表明SQL沒問題。若是你能看懂了執行計劃。那麼恭喜你,優化問題你已經解決了80%。由於發現問題每每比解決問題更困難。

解決問題

上面說到了如何發現問題,其實除了看執行計劃還有其它手段,如查看單個SQL執行次數等等,可是,執行計劃是開發人員最經常使用最直接的方式,可以幫助咱們解決大部分問題。那麼問題發現了以後如何解決呢?

簡單的說優化有兩個方向:能少作事儘可能少作事,若是不能少作事儘可能利用起服務器的性能。

如何提升服務器性能,能夠利用併發的方式讓服務器的資源儘可能發揮到極致。有些數據庫自己就提供了一些併發機制,如oracle能夠經過增長hint來設置SQL的併發數,也能夠經過應用程序的併發來儘可能壓榨出服務器的性能。固然還能夠經過增長服務器,對數據庫進行分庫分表來提高性能。

另一種方式是儘可能少作事(敲黑板,劃重點,這很關鍵)。

如何讓數據庫少作事呢?方式也有不少,最多見的是經過索引。曾經有一位在ORACLE從業20餘年的專家說了這樣一句話,「其實我只懂一點IT知識,IT知識裏我只懂一點ORACLE,而ORACLE我也只懂一點數據庫,數據庫裏面只懂點SQL,SQL裏面只懂點索引」。可見索引對於數據庫優化有多麼重要。

聊聊索引

前面也提到索引對於數據庫的優化特別重要,接下來聊聊建立和使用索引時的一些注意事項。索引建立的前綴性和可選擇性,索引建立的幾條建議,常見的索引被抑制狀況。

索引的前綴性

先看如下例子假設在員工表(emp)的(ENAME, JOB, MGR)三個字段上建了一個索引,例如索引名叫IDX_1。三個字段分別爲員工姓名、工做和所屬經理號。而後,寫以下一個查詢語句,並不斷進行查詢條件和次序的排列組合,例如:

Select * from emp where ENAME=’a’ and JOB=’b’ and MGR=3;
Select * from emp where JOB=’b’ and MGR=3 and ENAME=’a’;
Select * from emp where JOB=’b’ and ENAME=’a’ and MGR=3;
Select * from emp where JOB=’b’ and MGR=3;
Select * from emp where ENAME=’a’ and MGR=3;
Select * from emp where ENAME=’a’;
Select * from emp where JOB=’b’;
Select * from emp where MGR=3;
複製代碼

在各類條件組合狀況下,剛纔建的索引(IDX_1)是用仍是不用?也就是說對emp表的訪問是全表掃描和仍是按索引(IDX_1)訪問?

答案是隻要有ENAME=’a’條件,就能用上索引(IDX_1),而不是全表掃描。建立複合索引時必定要考慮到索引的前綴性不然會因爲沒有前綴列在檢索條件中致使的全表掃描。

索引的前綴性指的是必須用到索引的第一個字段。

索引的可選擇性

索引的可選擇性,指的是不重複的索引值(基數)和表記錄數的比值。可選擇性是索引篩選能力的一個指標。當可選擇性越大,索引價值也就越大。

如一張訂單表order記錄爲10萬條,表中user_id列的不重複值爲10000,order_date列不重複值爲1000,則建立在user_id上建立索引的查詢效率要比在order_date上建立索引的查詢效率高。這是由於,字段值越多,可選性越強,按照索引查詢後須要定位的記錄越少,查詢效率越高。

幾條建立索引的建議

數據庫最經常使用的索引爲B樹索引(不一樣的數據庫實現稍有不一樣,例如:oralce建立的是B*樹,mysql是B+樹),不一樣數據庫可能還有本身特有的索引如:oracle的位圖索引,mysql的hash索引等等。這裏咱們只討論經常使用的B樹索引。下面給出幾條建立B樹索引時設計單字段索引和複合索引的建議:

  1. 分析SQL語句中的約束條件字段,若是約束條件字段比較固定,則優先考慮建立針對多字段複合索引。例如同時涉及到多個字段的條件,則能夠考慮創建一個複合索引。
  2. 若是單個字段是主鍵或惟一字段,或者可選性很是高的字段,儘管約束條件字段比較固定,也不必定要建成複合索引,可建成單字段索引,下降複合索引開銷。
  3. 在複合索引設計中,需首先考慮複合索引第一個設計原則:複合索引的前綴性。即SQL語句中,只有複合索引的第一個字段做爲約束條件,該複合索引纔會啓用。
  4. 在複合索引設計中,其次應考慮複合索引的可選性。即按可選性高低,進行復合索引字段的排序。
  5. 若是條件涉及的字段不固定,組合比較靈活,則分別爲不一樣的列創建單字段索引。
  6. 若是是多表鏈接SQL語句,注意是否能夠在被驅動表(drived table)的鏈接字段與該表的其它約束條件字段上,建立複合索引。
  7. 經過多種SQL分析工具,分析執行計劃並以量化形式評估效果。

常見的索引被抑制狀況

在瞭解了索引建立的規則後根據業務須要建立好了索引,可是經過看語句的執行計劃發現仍然走的全表掃描,建立的索引沒有被使用。那麼索引不被使用都有可能由於什麼緣由致使的呢。

1.索引列上有表達式或者函數操做。則索引是失效的。

若有以下語句:

select user_name from user where age -30 = 0

select user_name from user where age  = 30
複製代碼

雖然age列上建立了索引,可是第一條語句依然是會按照全表掃描來執行的

2.存在隱式數據類型轉換

若有以下語句:

select user_name from user where age  = ‘30’
複製代碼

可是user表定義的age列爲number類型,在執行查詢時發生了隱式類型轉換。則索引被抑制。這種狀況在開發過程當中沒有上一種狀況明顯。很容易被你們忽視。

3.數據可選性不高

例如user表有10萬條數據,可是性別列只有男女兩種,這種狀況下,即便建立了索引,執行語句時也不會走索引。緣由是根據索引查找出的結果集依然很大,查詢效率還不如全表掃描的效率高,這是數據庫就會執行全表掃描。

4.忽略的索引的前綴性

如上文所述,執行語句時,忽略了索引的前置性,則執行語句時是不會走索引的。

使用is null或者is not null,null值並無被定義,因此索引會被抑制。

再聊一點

關於執行計劃

前面也提到SQL的優化很大程度上要依賴執行計劃,那麼執行計劃是如何生成出來的呢。簡單的說,數據庫會按期的收集數據庫中每一個表的數據量等基礎信息。當一條SQL發送到數據庫要執行以前,在完成合法性檢查以後數據庫會根據每張表的數據量,索引等信息經過計算給出一個SQL執行的最優計劃。

關於綁定變量

上文提到SQL的執行計劃須要經過各類信息計算獲得,那麼若是我把SQL的執行計劃緩存起來。那麼每次當同一個SQL執行屢次的時候不就免去了每次計算的代價嗎。實際上數據庫也正是這樣作的。數據庫會根據每條SQL的hash值將執行計劃緩存到內存。

爲了提升緩存SQL的命中率,咱們寫SQL的時候更多的使用佔位符。而不是直接傳值。

若有以下SQL:

select user_name from user where user_id = 1
select user_name from user where user_id = 2
複製代碼

這時數據庫會當作兩條SQL來處理。由於緩存並無命中。作以下修改:

select user_name from user where user_id = ?
複製代碼

每次經過綁定不一樣的參數則會命中緩存,減小了SQL的解析代價

那麼是否是全部的變量都要以綁定變量的方式來傳入呢?固然也不是。看下面的例子

若有訂單表t_order(user_id, order_date,amount),其中user_id爲用戶ID,order_date爲訂單日期

分別建立索引 :

idx_order_1(user_id)
idx_order_2(order_date)
複製代碼

若是我但願查詢的SQL爲:

select user_id, order_date,amount
from t_order
where user_id >= :num1
and user_id <= :num2
and order_date >= :date1
and order_date <= :date2
複製代碼

這時當傳入的參數num1和num2之間範圍較小極限狀況下num1=num2,也就是說我要查詢某一我的一段時間的訂單,則使用idx_order_2的效率會更高。而當傳入的參數num1和num2之間範圍較大但date1和date2的範圍較小時。如查詢某一天全部人的訂單,則使用idx_order_1的效率會更高。這時若是咱們使用綁定變量的方式。因爲數據庫分析執行計劃時並不能知道參數的範圍是什麼。也就不可以給出最優的計劃,這時就不建議使用綁定變量方式來傳值。

本文也只是對數據庫常見的優化方式 進行了討論。要作好數據庫的優化還要在平常開發中多多嘗試。

做者介紹

李光明,民生科技有限公司,用戶體驗技術部Firefly移動金融開發平臺Java開發工程師。

相關文章
相關標籤/搜索