mysql索引設計

原地址:https://zhuanlan.zhihu.com/p/25235279

轉載轉發請註明作者及來源

1. Sql執行順序

1. FROM 子句 組裝來自不同數據源的數據

2. WHERE 子句 基於指定的條件對記錄進行篩選

3. GROUP BY 子句 將數據劃分爲多個分組

4. 使用聚合函數進行計算

5. 使用HAVING子句篩選分組

6. 計算所有的表達式

7. 使用ORDER BY對結果集進行排序

8. select 獲取相應列

9. limit截取結果集

1.2 相應Case:

需求:查詢今日增長數據(根據video_id去重)

1. 錯誤:select * from tb where date(created_at) = current_date() group by video_id

2. 正確:select * from (select * from tb group by video_id ) as tb1 where tb1.date(created_at) = current_date()

**錯誤原因:group by 操作在where後執行。所以,第一個語句 是查詢今日 去重後數據,去重是在今日抓取的數據中去重。而且我們的需求是,對錶中所有數據去重,然後獲取今日新增長的的數據**

2. 索引基礎

2.1 BTree索引

mysql默認存儲引擎innodb只顯式支持B樹索引,對於頻繁訪問的表,innodb會透明建立自適應hash索引,即在B樹索引基礎上建立hash索引,可以顯著提高查找效率,對於客戶端是透明的,不可控制的,隱式的。

B Tree: 【更爲細緻的B樹索引講解請參考文末4.2延伸閱讀 】

m階 B樹的特性:

樹中每個節點至多包含m棵子樹

若根節點不是葉子節點,則至少包含兩顆子樹

除根以外的所有非終點節點至少有 (m/2)向上取整棵子樹

支持範圍查詢,前綴匹配查詢,等值查詢,可以避免排序,例如order by index相關的列,排序會非常快,因爲該列本身就是有序存儲的,查找時間複雜度 log m N(m爲底,N的對數,N爲總記錄數)


Hash索引特性

只支持包括 “=” "in "在內的等值查詢,不支持範圍,前綴匹配查詢

Hash索引是通過hash函數將,鍵值直接映射爲物理存儲地址,使時間複雜度降低到O(1).本身存儲是無序的,所以不能通過hash索引避免排序

2.2 Mysql聚簇索引

B-樹和B+樹的區別在於B+樹所有鍵值全部保存在葉子節點,而B-樹則不然,B-樹的鍵值根據樹的結構分佈在整個樹上。而Mysql爲什麼要採用B+樹索引呢?

1.遍歷方便.B+樹可以將鍵值保存在(線性表【數組或鏈表】)中,遍歷線性表比索引樹要快,因爲保存在線性表中數據存儲更加密集,B-Tree分散的存儲會導致更多的隨機I/O,對於磁盤訪問,隨機I/O是比順序I/O慢很多的,因爲隨機I/O需要額外的磁頭尋道操作。順序I/O有效減少尋道的次數

2.插入更新索引樹時可以避免移動節點.

3.遍歷任何節點的時間複雜度相同,即訪問路徑總是從根節點到葉子節點.相比B-樹,訪問時間略長.所以某些高頻訪問的搜索採用B-樹,即訪問頻率越高使其距離根節點越近。

4.(也許是最重要的)範圍查找方便。對於[A,B]區間的範圍查找,B-樹索引可以直接找到A,B對應的線性表中節點,只需要返回區間的所有節點即爲目標結果。而B-樹則稍顯麻煩需要繼續遍歷索引樹。

聚簇索引:將表中一條記錄存儲在索引的葉子節點中(也可能保存記錄的物理地址[可能是磁盤或者扇區號也可能是文件名及對應偏移量]的指針,如果在內存中即爲內存地址)。一般情況下mysql中使用主鍵 做聚簇索引

1. 一個表只能有一個聚簇索引。(一條記錄物理存儲只有一份)

2. 非聚簇索引中葉子節點的記錄中需要保存主鍵,如需訪問記錄中其他部分還需要,通過主鍵回表查詢。即兩次索引查找

?有人疑問非聚簇索引中爲什麼不保存記錄項的物理地址呢?當然可以記錄物理地址,但是主鍵索引更新操作帶來的索引分裂合併會改變其物理地址,這樣索引的維護代價比較大,而即使回表查詢,主鍵查找速度一般較快,影響不大。另外也可以通過覆蓋索引【即索引項覆蓋了select中的項】避免回表查詢

3.訪問聚簇索引速度應該保證足夠快,主鍵不宜選擇過大存儲需求的字段,例如UUID,另外非聚簇索引需要額外保存主鍵,主鍵太長存儲需求較大。也不宜選擇字符串:一.字符串比較速度較數字慢,二.字符串插入時更加無序,索引樹分裂合併相對更加頻繁,出現更多磁盤碎片 。當有字符串和數字都能滿足代理主鍵【該主鍵與業務無關只是添加一列主鍵保證記錄唯一性】需求時,應當優先選擇數字做主鍵,但是如果邏輯主鍵【業務中有作爲主鍵的列,也可選爲主鍵,即爲邏輯主鍵】是字符串類型,那也應該選擇其作爲主鍵,因爲字符串相比數字性能差別不是很大。

3. 索引常見優化方法

3.1 不能使用索引,不建議使用索引等常見誤區

1.數據類型爲Text,Blob等大對象不能建立索引,也不適合建立索引,另外字段太長的字段不適合建立索引。例如超長字符串。會使索引樹過大,mysql可能無法將其放入內存,訪問索引會帶來過多的磁盤I/O。效率低下

2.查詢表達式索引項上有函數.例如date(created_at) = 'XXXX'等.字符處理等。mysql將無法使用相應索引【查詢表達式相應列不能使用函數,但是右邊的值可以使用函數,例如 created_at < now() and created_at > current_date()

3.一次查詢(簡單查詢,子查詢不算)只能使用一個索引,

例如 where column1 = xxx and column2 = xxx order by column3

如果column1 ,column2 列各有一個索引,那麼只能使用其中之一的索引,

具體使用哪個索引,要看mysql的統計信息,mysql執行計劃中包括索引的選擇,具體的選擇要看哪個的索引選擇率更高【唯一值/總記錄數=選擇率,0<選擇率<=1 選擇率越大,說明給定一個值可以過濾更多的行,即過濾性更高】。但是mysql的統計信息不是精確實時的。所以可能存在使用“錯誤的索引”的情況,這時可以強制使用某個索引

select * from tb1 as t use index(index_name) ......

但是強烈不推薦使用這種方式,可以將其作爲臨時方案使用,應該首先考慮優化索引設計,例如,上述Case就應該建立(column1,column2,column3)或(column2,column1,column3) 聯合索引

4. where後的查詢表達式順序不能決定使用哪個索引.如column1 =xxx and column2 = xxx, 但並不代表優先使用column1 在前,column2在後的聯合索引。使用哪個索引由相應索引項的選擇率決定,最終判定標準是:掃描最少的行.使用索引過濾儘可能多的行。然後使用where中其他條件對 索引過濾後的結果集 一行行地判斷 完成where條件過濾。

5.修改過於頻繁的列使用索引要慎重.1s幾十次的修改就要注意了,過於頻繁的更新對於索引負擔太重,磁盤負載過重,另外更新操作可能會鎖住相關記錄,有死鎖和事務超時可能。但是該使就使。這些問題可以通過分區分表或者緩存解決

6.選擇率低的列不適合建立索引。如果索引項對應cardinality較小,例如小於10,那麼使用索引時就需要考慮是否有必要。因爲訪問索引的代價可能比全表掃描還要高。索引需要訪問索引文件,然後訪問葉子節點,拿到主鍵回表查詢,如果結果集比較大,這個代價極可能大於全表掃描【全表掃描是順序I/O,索引訪問會涉及更多隨機I/O,隨機I/O比順序I/O慢多了】。業務中常見的狀態列,在設計之初,這一列的cardinality基數【唯一值的個數】即是固定的,隨着記錄數增加,選擇率會越來越低,索引效率反而越來越低。可以考慮不建索引,或者將其作爲聯合索引的第一項

7.Mysql中對於唯一性檢查即聲明unique的列,自動建立唯一性索引,不需要再額外建立索引

8.不應該對where中每一個查詢條件都建立上索引,mysql只會使用其中一個索引,過多的索引帶來冗餘,導致一些索引被“浪費”,同時mysql在生成執行計劃時,需要考慮更多的索引,給查詢優化帶來更多工作,過多的索引還會給更新操作帶來更沉重的索引維護代價。應該簡化索引設計。同時利用聯合索引滿足多項條件的查詢

9.Order By ,Group By 可以利用索引避免排序。但是 存在where 語句下 只能使用where 查詢中使用的索引,例如where中使用了(A,B,C,D)聯合索引的A,B項,如果order by ,或者group by中存在C,或者(C,D)即可使用聯合索引,如果where中沒有使用索引,那麼即使order by,group by列中有索引也不能使用。即優先根據where 查詢使用索引,然後根據where中使用的索引再決定,order by,group by是否可以 使用到索引

10.當數據量達到千萬級別以上,索引本身就很大,無法裝入內存,訪問索引帶來的磁盤隨機I/O 開銷很大,索引性能下降較快,當併發量不大情況下,建立分區表可有效提高速度,因爲分區表的索引結構是互相獨立的,可單獨裝入內存,減少磁盤訪問。

11.更新刪除時指定索引列【事務特性,及隔離級別不熟悉同學請參考 延伸閱讀4.1】,mysql在默認的事物隔離級別是序列化解決了幻讀,並且通過間隙鎖,多併發版本讀提高了併發訪問性能,幻讀是指:一個事務中,當用戶查詢一個範圍中的結果時,另一個事務執行了相應的插入刪除操作,導致兩次查詢結果不同,少了或多了一些行,就像幻象一樣。mysql 解決幻讀有兩種方案:一.對於查詢select操作只是針對本事務開啓時刻的“鏡像”查詢。例如本事務開啓後,其他事務插入刪除了相關數據並提交,本事務是無法察覺的。實現方式爲 版本控制。二.更新刪除【包括 select ………… for update 】等寫操作涉及到範圍更新時,如果查詢條件where中存在索引,即鎖住索引樹的相關鍵值段例如 更新 id主鍵索引在 1-100的數據,那麼它會鎖住 1- 100 這些記錄的 id 索引,其他事務更新這個範圍數據時,會進入鎖等待,直到擁有鎖的事務,或者等待超時。如果查詢條件中不能使用索引,mysql爲了實現序列化的隔離級別,會對全表加鎖,任何寫操作不能進行。當併發寫操作多,事務時間長時,會出現較多鎖等待及等待超時事務。需要通過添加索引,及減小事務粒度或者降低mysql默認隔離級別方式解決此類問題。

3.2 索引設計的幾個“原則”

1. 索引的設計應該與業務需求息息相關,沒有完美的索引設計,只有滿足需求的索引設計,項目前期設計的索引不可能完美的滿足後期的需求。應隨時根據業務合理取捨。

2. 索引設計應該優先照顧查詢最爲頻繁,或業務優先級高,與用戶相關的查詢。如果我們可以忍受,那麼可以不建索引

3.使用短索引,索引長度不宜過大,利用B Tree的特性使用最左匹配查找高效利用索引第一列、對選擇率高的列索引、使用覆蓋索引避免回表查詢

4.及時刪除不再使用的索引,例如發現(A,B)不滿足需求,新加一項(A,B,C)即可刪除舊索引(A,B)

3.3 聯合索引的順序問題

1.聯合索引設計時,索引順序是很重要的。當聯合索引中,每一列的查詢頻率都相差不多時,可以優先將選擇率最高的列作爲聯合索引第一列,這樣第一列即可過濾更多列,效率更高。由於聯合索引第一列可以單獨使用,例如聯合索引(column1,column2,column3,column4)即可滿足 where column1 =xxx 也可滿足 where column1 = xxx and column2 = xxx and column3 =xxx and column4 = xxx的需求,這樣不需要爲第一列的獨立查詢額外建立單列索引

2.使用部分前綴索引鍵,按照聯合索引聲明順序查詢。例如索引(A,B,C) 只能匹配 where A = x xx ,where A = XXX and B = xxx ,where A = xxx and B = xxx and C = xxx ,不能跳過前一列,匹配後一列. 例如 where A = xxx and c = xxx 這時雖然可能也使用該索引,但是隻能使用一部分,匹配A列,而B,C列不能匹配。

3.前綴匹配,與範圍匹配。 BTree索引可以使用前綴匹配,例如 where A like "xxx%" ,使用前綴索引後,就不能使用前綴列的後續索引列。

4.group by,order by 本質是對where查詢出的結果集進行排序操作,當待排序列匹配 where 中索引順序時纔可避免排序,直接通過索引即可返回有序結果集,例如我們需要將查詢結果按照評分排名,那麼就可以考慮將rank列放在聯合索引的最後一列。(X, …… ,rank)。當查詢結果比較大時,可以考慮這樣設計

5.limit 分頁查詢 .limit 使用時必須排序否則可能出現不同頁返回重複數據的風險。limit 返回某一位置的給定偏移量的記錄,但是它的順序依賴於存儲位置順序,索引順序,所以分頁時不同頁會有出現重複數據的風險。limit 操作前需要添加order by 進行排序。由於訪問非聚簇索引時,mysql 有一個優化操作,當訪問非聚簇索引,回表查詢時,mysql 會對主鍵進行排序,目的是:聚簇索引是按順序存儲記錄,對主鍵排序後,訪問聚簇索引可以更加順序的訪問磁盤,減少隨機I/O,提高速度,**所以當分頁沒有特別指定的列時,指定主鍵排序即可**,另外不需要在聯合索引最後一列添加主鍵,因爲它本身包含主鍵 【非聚簇索引不存儲完整記錄,通過訪問主鍵索引找到完整記錄 】。


3.4 索引設計優化常見小技巧

以上已經列出較多的誤區及注意事項,理解即可,更重要的是根據業務對索引取捨的經驗。更多的設計技巧希望同學們在實踐中自己總結並分享出來。

1.數據量較大的表(千萬以上)考慮是否適合建立分區。

2.對於較長字符串例如200以上,可以考慮單獨增加索引列,對其整體hash或者去其中一部分hash後存入其他一列,這樣將字符串查找變成數字查找,同時索引長度大大減小,可有效提高索引速度,降低索引大小。但是需要考慮hash函數的“碰撞”問題,選擇適合的hash函數。

3.使用explain命令查看sql 的執行計劃,請參考延伸閱讀4.3


4.延伸閱讀

4.1 數據庫事務及隔離級別概要

數據庫事務的四個標準特徵(ACID):

原子性:一個事務必須被一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事務來說,不可能只執行其中一部分操作,

一致性:數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。指關聯數據之間的邏輯關係是否正確和完整,一致性處理數據庫中對所有語義約束的保護。假如數據庫的狀態滿足所有的完整性約束,就說該數據庫是一致的。

隔離性:通常一個事務所做的修改在最終提交以前,對其他事務是不可見的,多個事務併發訪問時,事務之間是隔離的,一個事務不應該影響其它事務運行效果。這指的是在併發環境中 ,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看到中間狀態的數據。

持久性:意味着當系統或介質發生故障時,確保已提交事務的更新不能丟失。即一旦一個事務提交,DBMS保證它對數據庫中數據的改變應該是永久性的,耐得住任何系統故障。持久性通過數據庫備份和恢復來保證。

對於事務的隔離性而言有四種隔離級別:

Read Uncommitted(讀取未提交內容):在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因爲它的性能也不比其他級別好多少。讀取未提交的數據,也被稱之爲髒讀(Dirty Read)。

Read Committed(讀取提交內容)

這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支持所謂的不可重複讀(Nonrepeatable Read),因爲同一事務的其他實例在該實例處理其間可能會有新的commit,所以同一select可能返回不同結果。

由於正在讀取的數據只獲得了讀取鎖,讀完之後就解鎖,不管當前事務有沒有結束,這樣就容許其他事務修改本事務正在讀取的數據。導致不可重複讀。

解決不可重複讀的問題就要求,對正在讀取的若干行加上行級鎖。要求在本次事務中不可修改這些行。解決ReadCommited更側重數據行不可更新。

Repeatable Read(可重讀)

這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

解決幻讀的方案應該是在表上加鎖,幻讀出現的場景主要是插入操作,由於插入操作使得事務不同的查詢中出現不同的結果。

Serializable(可串行化) 這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也越大。對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設爲Read Committed。它能夠避免髒讀取,而且具有較好的併發性能。儘管它會導致不可重複讀、幻讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,可以由應用程序採用悲觀鎖或樂觀鎖來控制。(樂觀鎖通過版本號控制是否存在不可重複讀情況,如果不存在則提交,否則事務回滾。悲觀鎖是通過數據庫系統本身在內部加鎖,鎖住要更新的數據,不允許其他事務修改,但是會消耗大量的性能)

4.2 磁盤訪問及B +,B-樹索引基礎

參考鏈接: 討論MySQL索引底層實現_Mysql_數據庫-ITnose

磁盤讀寫:guisu,程序人生。 逆水行舟,不進則退。

### 4.3 explain命令講解

參考鏈接: 8.3.1 Explain 的使用(2)

ID:MySQL Query Optimizer選定的執行計劃中查詢的序列號。

Select_type:所使用的查詢類型,主要有以下這幾種查詢類型。

DEPENDENT SUBQUERY:子查詢內層的第一個SELECT,依賴於外部查詢的結果集。

DEPENDENT UNION:子查詢中的UNION,且爲UNION中從第二個SELECT開始的後面所有SELECT,同樣依賴於外部查詢的結果集。

PRIMARY:子查詢中的最外層查詢,注意並不是主鍵查詢。

SIMPLE:除子查詢或UNION之外的其他查詢。

SUBQUERY:子查詢內層查詢的第一個SELECT,結果不依賴於外部查詢結果集。

UNCACHEABLE SUBQUERY:結果集無法緩存的子查詢。

UNION:UNION語句中第二個SELECT開始後面的所有SELECT,第一個SELECT爲PRIMARY。

UNION RESULT:UNION 中的合併結果。

Table:顯示這一步所訪問的數據庫中的表的名稱。

Type:告訴我們對錶使用的訪問方式,主要包含如下集中類型。

all:全表掃描。

const:讀常量,最多隻會有一條記錄匹配,由於是常量,實際上只須要讀一次。

eq_ref:最多隻會有一條匹配結果,一般是通過主鍵或唯一鍵索引來訪問。

fulltext:進行全文索引檢索。

index:全索引掃描。

index_merge:查詢中同時使用兩個(或更多)索引,然後對索引結果進行合併(merge),再讀取表數據。

index_subquery:子查詢中的返回結果字段組合是一個索引(或索引組合),但不是一個主鍵或唯一索引。

rang:索引範圍掃描。

ref:Join語句中被驅動表索引引用的查詢。

ref_or_null:與ref的唯一區別就是在使用索引引用的查詢之外再增加一個空值的查詢。

system:系統表,表中只有一行數據;

unique_subquery:子查詢中的返回結果字段組合是主鍵或唯一約束。

Possible_keys:該查詢可以利用的索引。如果沒有任何索引可以使用,就會顯示成null,這項內容對優化索引時的調整非常重要。

Key:MySQL Query Optimizer 從 possible_keys 中所選擇使用的索引。

Key_len:被選中使用索引的索引鍵長度。

Ref:列出是通過常量(const),還是某個表的某個字段(如果是join)來過濾(通過key)的。

Rows:MySQL Query Optimizer 通過系統收集的統計信息估算出來的結果集記錄條數。

Extra:查詢中每一步實現的額外細節信息,主要會是以下內容。

Distinct:查找distinct 值,當mysql找到了第一條匹配的結果時,將停止該值的查詢,轉爲後面其他值查詢。

Full scan on NULL key:子查詢中的一種優化方式,主要在遇到無法通過索引訪問null值的使用。

Range checked for each record (index map: N):通過 MySQL 官方手冊的描述,當 MySQL Query Optimizer 沒有發現好的可以使用的索引時,如果發現前面表的列值已知,部分索引可以使用。對前面表的每個行組合,MySQL檢查是否可以使用range或 index_merge訪問方法來索取行。

SELECT tables optimized away:當我們使用某些聚合函數來訪問存在索引的某個字段時,MySQL Query Optimizer 會通過索引直接一次定位到所需的數據行完成整個查詢。當然,前提是在 Query 中不能有 GROUP BY 操作。如使用MIN()或MAX()的時候。

Using filesort:當Query 中包含 ORDER BY 操作,而且無法利用索引完成排序操作的時候,MySQL Query Optimizer 不得不選擇相應的排序算法來實現。

Using index:所需數據只需在 Index 即可全部獲得,不須要再到表中取數據。

Using index for group-by:數據訪問和 Using index 一樣,所需數據只須要讀取索引,當Query 中使用GROUP BY或DISTINCT 子句時,如果分組字段也在索引中,Extra中的信息就會是 Using index for group-by。

Using temporary:當 MySQL 在某些操作中必須使用臨時表時,在 Extra 信息中就會出現Using temporary 。主要常見於 GROUP BY 和 ORDER BY 等操作中。

Using where:如果不讀取表的所有數據,或不是僅僅通過索引就可以獲取所有需要的數據,則會出現 Using where 信息。

Using where with pushed condition:這是一個僅僅在 NDBCluster存儲引擎中才會出現的信息,而且還須要通過打開 Condition Pushdown 優化功能纔可能被使用。控制參數爲 engine_condition_pushdown 。

Impossible WHERE noticed after reading const tables:MySQL Query Optimizer 通過收集到的統計信息判斷出不可能存在結果。

No tables:Query 語句中使用 FROM DUAL或不包含任何 FROM子句。

Not exists:在某些左連接中,MySQL Query Optimizer通過改變原有 Query 的組成而使用的優化方法,可以部分減少數據訪問次數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章