1. 程式人生 > 其它 >Mysql - 字首索引

Mysql - 字首索引

字首索引
1.字首索引的優劣
很多情況下,我們需要根據一個長字串型別的欄位去查詢記錄,比如身份證,郵箱,為了避免全表掃描,就需要為字串欄位新增索引。

由於Mysql支援字首索引,所以我們可以選擇將整個欄位新增索引,或者只將前一部分的字串加上索引:

#整個欄位
alter table T add index index1(email);
#一部分欄位
alter table T add index index2(email(6));

假設我們執行一條查詢sql:

select id,name,email from SUser where email='[email protected]';
1
對於完整索引:
從 index1 索引樹找到滿足索引值是’[email protected]’的這條記錄,取得 ID2 的值;
到主鍵上查到主鍵值是 ID2 的行,判斷 email 的值是正確的,將這行記錄加入結果集;
取 index1 索引樹上剛剛查到的位置的下一條記錄,發現已經不滿足 email='[email protected]’的條件了,迴圈結束。

而對於字首索引:
從 index2 索引樹找到滿足索引值是’zhangs’的記錄,找到的第一個是 ID1;
到主鍵上查到主鍵值是 ID1 的行,判斷出 email 的值不是’[email protected]’,這行記錄丟棄;
取 index2 上剛剛查到的位置的下一條記錄,發現仍然是’zhangs’,取出 ID2,再到 ID 索引上取整行然後判斷,這次值對了,將這行記錄加入結果集;
重複上一步,直到在 index2 上取到的值不是’zhangs’時,迴圈結束。
根據這個流程,我們不難發現字首索引有以下問題:

索引覆蓋失效:由於字首索引在命中以後,必須再回主鍵索引樹確定一次,所以索引覆蓋對字首索引來說是無效的。
回表次數多:使用字首索引後,可能會導致查詢語句讀資料的次數變多。

2.如何選擇合適的長度
字首索引需要有足夠的區分度才能提高查詢效率。比如有ABCC,ABDD,ABEE三條資料,選前兩個個字元作為索引等於沒加索引,選前三個字元作為索引就很合適。當然,實際情況肯定會更復雜,我們就需要更具體的分析。

首先,算出這個列上有多少個不同的值:

select count(distinct email) as L from T;
1
依次選取不同長度的字首來看這個值,比如我們要看一下 4~7 個位元組的字首索引,可以用這個語句:

select
count(distinct left(email,4))as L4,
count(distinct left(email,5))as L5,
count(distinct left(email,6))as L6,
count(distinct left(email,7))as L7,
from T;
1
2
3
4
5
6
使用字首索引必然會損失一部分割槽分度,所以我們需要預先設定一個可以接受的損失比例,比如 5%。然後,在返回的 L4~L7 中,找出不小於 L * 95% 的值,然後選擇最短的長度。

3.其他優化方式
對於郵箱,字首索引效果還比較明顯,因為@之前的字串一般不會有太多的相似度,但是對於比如像身份證這樣,同一個縣市裡的市民只有後幾位才會有較大區別的長字串,可能就需要設定一個非常長的字首索引了,這顯然不是我們樂意見到的。

倒序儲存
我們可以藉助reverse()函式實現倒序儲存。比如身份證存入的時候我們可以倒序儲存,查詢的時候也先反轉在查詢。這樣加索引以後只需要選擇前幾位辨識度高的即可。

Hash欄位
我們藉助crc32/64()函式去獲取長字串的校驗碼,在表上另外開一個欄位用於儲存對應的校驗碼,以長度較短的校驗碼作為索引。不過由於crc32仍然會出現值重複的情況,所以查詢的時候還需要判斷拿到的記錄是否與條件欄位完全一致。

他們的異同如下:
都不支援範圍查詢
佔用空間:倒序儲存方式在主鍵索引上,不會消耗額外的儲存空間,而 hash 欄位方法需要增加一個欄位。當然,倒序儲存方式使用 4 個位元組的字首長度應該是不夠的,如果再長一點,這個消耗跟額外這個 hash 欄位也差不多抵消了。
額外消耗:序方式每次寫和讀的時候,都需要額外呼叫一次 reverse 函式,而 hash 欄位的方式需要額外呼叫一次 crc32() 函式。如果只從這兩個函式的計算複雜度來看的話,reverse 函式額外消耗的 CPU 資源會更小些。
查詢效率:使用 hash 欄位方式的查詢效能相對更穩定一些。因為 crc32() 算出來的值雖然有衝突的概率,但是概率非常小,可以認為每次查詢的平均掃描行數接近 1。而倒序儲存方式畢竟還是用的字首索引的方式,也就是說還是會增加掃描行數。
當然,還有一種折中的方法,就是拆分欄位:

對於像郵箱這樣的欄位,有時候@後面的欄位往往都是固定的幾種,可以單獨拆分出來作為一個欄位,@前的作為單獨的欄位直接加全欄位索引,這樣減少的欄位長度,並且保證也了範圍查詢的效能。