假設要維護乙個支援郵箱登入的系統,使用者表如下定義:1
5mysql> create table suser(
id bigint unsigned primary key,
email varchar(64),
)engine=innodb;
由於使用郵箱登入,登入的時候一定要根據郵箱去查詢。所以業務**一定會出現一下語句:1mysql> select id, email from suser where email=
因此 email 字段必須加上索引,否則這個語句會做全表掃瞄。
1.完整索引
直接給 email 字段加上索引1mysql> alert table suser ;
該語句建立的 index1 索引,包含了每個記錄的整個字串,索引示意圖如下:
index1 執行順序如下:從 index1 索引樹找到滿足索引值是 [email protected] 的記錄,取得 id2的值;
到該主鍵上插到主鍵值是 id2 的行,判斷 email 的值是正確的,將這行記錄加入結果集;
取 index1 索引樹上剛剛查到的位置的下一條記錄,發現不滿足 [email protected] 的條件,迴圈結束。
這個過程中,只需要回主鍵索引取一次記錄,所以系統只認為掃瞄了一行。
2. 字首索引
將某個欄位的前n位設為索引,下面語句建立 email(6) 索引結構1mysql> alert table suser add index index2(email(6));
該語句建立的 index2 索引,對於每個記錄只取了6個位元組,索引示意圖如下:
index2 執行順序如下:從 index2 索引樹找到滿足索引值是 『zhangs』 的記錄,找到第乙個是 id1;
到主鍵上查到主鍵值是 id1 的行,判斷 email 的值不是 [email protected],這行丟棄
取 index2 上剛剛查到的位置的下一條記錄,發現仍然是 『zhangs』, 取出 id2,再到 id 索引上取整行然後判斷,這次值對了,將這行記錄加入結果集;
重複上一步,直到在 index2 上取到的值不是 『zhangs』 時,迴圈結束。
這個過程中,要回主鍵索引取 4 次判斷,也就是掃瞄了 4 行。
通過對比,可以發現,使用字首索引後,可能會導致查詢語句讀資料的次數變多。
但是,對於這個查詢語句來說,如果定義的 index2 不是 email(6) 而是 email(7),也就是說取 email 欄位前7個位元組來構建索引的話,即滿足前桌 『zhangs』 的記錄只有乙個,也能夠直接插到 id2,只掃瞄一行就結束了。
也就是說使用字首索引,定義好長度就可以做到既節省空間,又不用額外增加太多的查詢成本。
如何確認字首索引的長度?
答案:區分度,區分度越高意味著重複值的鍵值越少。因此可以統計索引有多少不同的值來判斷要使用多長的字首。
首先,使用下面這個語句,算出這個列上有多少不同的值:1mysql> select count(distinct email) as l from suser;
然後,依次選取不同長度的字首來看這個值,比如我們要看一下4~7 個值位元組的字首索引:1
6mysql> select
truecount(distinct left(email, 4) as l4)
truecount(distinct left(email, 5) as l5)
truecount(distinct left(email, 6) as l6)
truecount(distinct left(email, 7) as l7)
from suser;
使用字首索引很有可能損失區分度,所以需要預先設定乙個損失比例,比如 5%。然後在返回的 l4~l7 中,找出不小於 l*95% 的值,假設這裡 l6、l7 都滿足,可以選擇字首長度為6。
字首索引對覆蓋索引的影響
字首索引不僅僅可能會增加掃瞄行數,還有可能影響效能。1mysql> select id,email from suser where email =
以上這個語句僅僅返回 id 和 email 字段。
所以,如果使用 index1 的話,可以利用覆蓋索引,從 index1 查詢到結果直接返回即可,無需回到 id 索引再去查一次。而如果使用 index2 的話,就不得不回到 id 索引再去判斷 email 欄位的值。
即使將 index2 的定義修改為 email(18) 的字首索引,這時候雖然 index2 包含了所有的資訊,但是 innodb還是要回到 id 索引再查一次,因為系統並不確定字首索引的定義是否截斷了完整資訊。
也就是說,使用字首索引就用不上覆蓋索引對查詢效能的優化。
3. 其他方式
對於郵箱這樣的字段來說,使用字首索引的效果可能還不粗,但是遇到字首的區分度不夠好的情況,該怎麼辦?
比如,身份證號碼,共 18 位,其中前 6 位是位址碼,所以同乙個縣的人身份證號前6位一般是相同的,因此如果對身份證號做長度為6的字首索引,區分度極低。
按照前面的方法,可能需要建立長度為12以上的字首索引,才能滿足區分度要求。但是索引選取的越長,占用的磁碟空間越大,相同的資料頁能放下的索引的值越少,搜尋效率也越低。
那麼如果確認業務需求只按照身份證號進行等值查詢的需求,可以使用以下方法
第一種倒序儲存 (如果儲存身份證號碼的時候把它倒過來存,每次查詢可以這麼寫)1mysql> select field_list from t where id_card = reverse('input_id_card_string');
由於身份證最後6位沒有位址碼這樣重複的邏輯,最後6位很有可能提供了足夠的區分度,
第二種使用 hash 字段 (在表上再建立乙個整數字段,來儲存身份證的校驗碼,同事在這個欄位上建立索引)1mysql> alter table t add id_card_crc int unsigned, add (id_card_crc);
然後每次插入新記錄的時候,都使用 crc32() 這個函式得到校驗碼填到這個新字段,由於校驗碼可能存在衝突,也就是說兩個不同的身份證號通過 crc32() 函式得到的結果可能是相同的,所以查詢語句 where 部分要判斷 id_card 的值是否精確相同。1mysql> select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string';
這樣索引長度變成了 4 個位元組,比原來小了很多。
兩種方法的異同點
同:不支援範圍查詢,只支援等值查詢
異:占用空間上,倒序儲存方式在主鍵索引上不會消耗額外的儲存空間,而 hash 字段防範需要增加乙個字段。
cpu 消耗方面,倒序方式每次讀寫,都需要額外呼叫一次 reverse 函式,而 hash 欄位的方式需要額外呼叫一次 crc32() 函式。如果從兩個函式的計算複雜度來看的話, reverse 函式額外消耗的 cpu 資源會更小點。
從查詢效率上看,使用 hash 字段方式的查詢效能相對更穩定一些。因為 crc32 算出來的值雖然有衝突的概率,但是概率非常小,可以認為每次查詢的平均掃瞄行接近1。而倒序儲存的方式畢竟還是用的字首索引的方式,也就是說會增加掃瞄行數。
總結字串字段建立索引總共有4種方式直接建立完整索引,這樣可能比較占用空間;
建立字首索引,節省空間,但是會增加查詢掃瞄次數,並且不能使用覆蓋索引
倒序儲存,再建立字首索引,用於繞過字串本身字首的區分度不夠的問題
建立 hash 字段索引,查詢效能穩定,有額外的儲存和計算消耗,跟第三種方式一樣,都不支援範圍掃瞄。
mysql中字串索引
1.只是用字串的最左邊n個字元建立索引,推薦n 10 比如index left address,8 但是需要知道字首索引不能在order by中使用,也不能用在索引覆蓋上。2.對字串使用hash方法將字串轉化為整數,address key hashtoint address 對address key...
mysql字串字首索引
比如,這兩個在 email 欄位上建立索引的語句 mysql alter table suser add index index1 email 或mysql alter table suser add index index2 email 6 第乙個語句建立的 index1 索引裡面,包含了每個記錄...
mysql字串亂碼 解決Mysql字串亂碼
1 字符集和字元序2 3 字符集 character set 定義了字元以及字元編碼。4 5 字元序 collation 定義了字元的比較規則。6 7 mysql支援多種字符集 與 字元序。8 9 乙個字符集對應至少一種字元序 一般是1對多 10 11 兩個不同的字符集不能有相同的字元序。12 13...