自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

超長(zhǎng)字符串字段,前綴索引兩宗罪

運(yùn)維 數(shù)據(jù)庫運(yùn)維
前綴索引并不是一個(gè)難理解的東西,但是這里面涉及到的一些細(xì)節(jié),我相信很多同學(xué)都沒有去深入了解過。

[[440636]]

本文轉(zhuǎn)載自微信公眾號(hào)「飛天小牛肉」,作者小牛肉。轉(zhuǎn)載本文請(qǐng)聯(lián)系飛天小牛肉公眾號(hào)。

前綴索引并不是一個(gè)難理解的東西,但是這里面涉及到的一些細(xì)節(jié),我相信很多同學(xué)都沒有去深入了解過。

老規(guī)矩,前綴索引相關(guān)面試題的背誦版在文末。點(diǎn)擊閱讀原文可以直達(dá)我收錄整理的各大廠面試真題

InnoDB 表中每一列索引的最大長(zhǎng)度不能超過 767 字節(jié),所以,對(duì)于某些比較長(zhǎng)的字段,如果確實(shí)有建立索引的必要,使用前綴索引不僅能夠避免索引長(zhǎng)度超過限制,而且相對(duì)于普通索引來說,占用的空間和查詢成本更小。

至于為什么說前綴索引占用的空間和查詢成本更小,我們來直接上個(gè)例子:

假設(shè)表中存在一個(gè)郵箱 email 字段,我們?cè)谶@個(gè)字段上面分別創(chuàng)建普通索引和前綴索引:

1)普通索引,包含了每行 email 記錄的的整個(gè)字符串:alter table user add index index1(email);

2)前綴索引,取每行 email 記錄的前 6 個(gè)字節(jié):alter table user add index index2(email(6));

你可以看到,由于 email(6) 這個(gè)索引結(jié)構(gòu)中每個(gè) email 字段都只取前 6 個(gè)字節(jié) zhangs,所以占用的空間比普通索引更小,這就是使用前綴索引的優(yōu)勢(shì)。

很好理解,對(duì)吧。

前綴索引一宗罪

但是,前綴索引這個(gè)占用空間更小的優(yōu)勢(shì)可能會(huì)帶來額外的記錄掃描次數(shù)。

舉個(gè)例子,執(zhí)行如下 sql 語句:

  1. select * from user where email = 'zhangs2001';  

1)對(duì)于普通索引 email 來說,執(zhí)行順序如下:

  • 從 index1 索引樹找到第一個(gè)滿足索引值是 'zhangs2001' 的這條記錄,并獲取到主鍵 ID2 的值;
  • 根據(jù)主鍵值回表查詢,獲取其他相應(yīng)的記錄,然后將獲取到的結(jié)果加入結(jié)果集;
  • 取 index1 索引樹上剛剛查到的位置的下一條記錄,發(fā)現(xiàn)已經(jīng)不滿足 email='zhangs2001' 的條件了,結(jié)束執(zhí)行

這個(gè)過程中,只需要回表一次

2)對(duì)于前綴索引 email(6) 來說,執(zhí)行順序如下:

  • 從 index1 索引樹找到第一個(gè)滿足索引值是 'zhangs' 的這條記錄,并獲取到主鍵 ID1 的值;
  • 根據(jù)主鍵值回表查詢,判斷 email 的值到底是不是 'zhangs2001',發(fā)現(xiàn)并不是,這行記錄丟棄
  • 取 index1 索引樹上剛剛查到的位置的下一條記錄,發(fā)現(xiàn) email 前綴仍然滿足 'zhangs',則獲取到主鍵 ID2 的值;然后根據(jù)主鍵值回表查詢,返現(xiàn) email 的值確實(shí)是 'zhangs2001',則將這行記錄加入結(jié)果集
  • 如此重復(fù),直到 email 前綴不再是 'zhangs',則執(zhí)行結(jié)束

可以看到,這個(gè)過程中,需要回表四次

這就是前綴索引的第一宗罪:使用前綴索引可能會(huì)增加記錄掃描次數(shù)與回表次數(shù),影響性能

不過呢,我們做一些細(xì)微的改變,就能讓這個(gè)前綴索引回表次數(shù)大大減少。

把 index2-email(6) 這個(gè)前綴索引改成 index3-email(7):

再來看上面這個(gè)例子,執(zhí)行順序如下:

  • 從 index1 索引樹找到第一個(gè)滿足索引值是 'zhangs2' 的這條記錄,并獲取到主鍵 ID2 的值;
  • 根據(jù)主鍵值回表查詢,判斷 email 的值到底是不是 'zhangs2001',發(fā)現(xiàn)確實(shí)是,則將這行記錄加入結(jié)果集
  • 取 index1 索引樹上剛剛查到的位置的下一條記錄,發(fā)現(xiàn) email 前綴不滿足 'zhangs2',則執(zhí)行結(jié)束

可以看到,相對(duì)于普通索引,email(7) 這個(gè)前綴索引同樣只需要回表一次,并且占用更少的索引空間。

前綴索引二宗罪

看下面這條 SQL 語句:

  1. select id,email from user where email = 'zhangs2001'

如果使用 index1 索引(即 email 整個(gè)字符串的索引結(jié)構(gòu))的話,可以利用上覆蓋索引,從 index1 索引樹上查到結(jié)果后就可以返回了,不需要進(jìn)行回表。

而如果使用 index2(即 email(6) 前綴索引結(jié)構(gòu))的話,就不得不再次根據(jù)主鍵值去回表判斷 email 字段的值是否真的是 'zhangs2001'。也就是說,使用前綴索引就用不上覆蓋索引對(duì)查詢性能的優(yōu)化了。

那有同學(xué)就要問了,如果是 email(10) 呢,這個(gè)前綴索引不就完全包含了 zhangs2001 的所有信息了嘛,還需要回表嗎?

答案是并不能阻止 InnoDB 的回表,因?yàn)?InnoDB 并不能確定前綴索引的定義是否截?cái)嗔送暾畔?。誰知道你會(huì)不會(huì)又增加一個(gè) 'zhangs20012' 的記錄呢,對(duì)吧。

如何定義前綴索引的長(zhǎng)度

索引選取的越長(zhǎng),占用的磁盤空間就越大,相同的數(shù)據(jù)頁能放下的索引值就越少,搜索的效率也就會(huì)越低。

在上面的例子中我們提到,只需要把前綴索引從 email(6) 改成 email(7),就可以大大減少記錄掃描和回表的次數(shù),所以,在定義前綴索引的時(shí)候,我們需要在占用空間和搜索效率之間做一個(gè)權(quán)衡 trade-off。

事實(shí)上,我們?cè)诮⑶熬Y索引時(shí)關(guān)注的是區(qū)分度,區(qū)分度越高,意味著重復(fù)的鍵值越少,所以區(qū)分度越高越好。

對(duì)于索引來說,什么是區(qū)分度呢,很簡(jiǎn)單,就是這個(gè)索引上有多少個(gè)不同的值。建立出來的索引上擁有越多不同的值,那么這個(gè)索引的區(qū)分度就越高。

因此,我們可以通過統(tǒng)計(jì)索引上有多少個(gè)不同的值來判斷要使用多長(zhǎng)的前綴??梢允褂孟旅孢@個(gè)語句,計(jì)算出 email 列上有多少個(gè)不同的值,記作 email_length:

  1. select count(distinct email) as email_length from user

然后,依次選取不同長(zhǎng)度的前綴來看區(qū)分度,比如我們要看前綴索引的長(zhǎng)度是 6~10 時(shí)候的區(qū)分度,可以用這個(gè)語句:

  1. select  count(distinct left(email,6))as email_length_6, 
  2.     count(distinct left(email,7))as email_length_7, 
  3.     count(distinct left(email,8))as email_length_8, 
  4.     count(distinct left(email,9))as email_length_9, 
  5.     count(distinct left(email,10))as email_length_10, 
  6. from user

當(dāng)然了,既然我們使用了前綴索引,那么就不可避免的會(huì)損失區(qū)分度,就像我們前面所說的,誰也不知道會(huì)不會(huì)又新增出一條記錄完全踩中前綴但是又不滿足判斷條件。所以我們需要預(yù)先設(shè)定一個(gè)可以接受的區(qū)分度損失比例,比如 5%。然后找出不小于 email_length * (1 - 5%) 的值,假設(shè)這里 email_length_8、email_length_9 都滿足,我們就可以選擇前綴長(zhǎng)度為 8。

前綴索引的區(qū)分度不夠高怎么辦

我當(dāng)時(shí)實(shí)習(xí)的時(shí)候就遇到過這個(gè)問題,字段(假設(shè)這個(gè)字段名是 a)超級(jí)超級(jí)長(zhǎng),遠(yuǎn)大于 InnoDB 的限制 767 字節(jié),普通索引肯定是不可能了,前綴索引就算是長(zhǎng)度定義成 767 都還是存在區(qū)分度不高的情況,但是又存在根據(jù)這個(gè)字段進(jìn)行查詢的挺頻繁的一個(gè)需求。

一個(gè)很常見的解決手段就是 Hash。

對(duì)這個(gè)超長(zhǎng)字段 a 進(jìn)行 hash(假設(shè)命名為 a_hash) 存入數(shù)據(jù)庫,然后對(duì)這個(gè) hash 值建立索引,由于 hash 值同樣可能存在沖突,也就是說兩個(gè)不同的 a 通過 Hash 函數(shù)得到的結(jié)果可能是相同的,所以我們?cè)诓樵冋Z句的 where 部分還需要進(jìn)行一次精確判斷

  1. # 假設(shè)輸入的字段是 input_a 
  2. select * from user where hash(input_a) = a_hash and input_a = a; 

不過使用 Hash 這種方式有個(gè)眾所周知的缺點(diǎn),那就是不支持范圍查詢了,只能等值查詢。

最后放上這道題的背誦版:

面試官:前綴索引了解嗎,為什么要建前綴索引

小牛肉:前綴索引就是選取字段的前幾個(gè)字節(jié)建立索引。首先,InnoDB 限制了每列索引的最大長(zhǎng)度不能超過 767 字節(jié),所以,對(duì)于某些比較長(zhǎng)的字段,如果確實(shí)有建立索引的必要,使用前綴索引不僅能夠避免索引長(zhǎng)度超過限制,而且相對(duì)于普通索引來說,占用的空間和查詢成本更小。

不過前綴索引可能會(huì)導(dǎo)致兩個(gè)問題:

第一個(gè),使用前綴索引可能會(huì)增加記錄掃描次數(shù)與回表次數(shù),影響性能。針對(duì)這一點(diǎn)呢,其實(shí)前綴索引長(zhǎng)度的選取還是很重要的,可能前綴定義的長(zhǎng)一點(diǎn),就能夠大幅減少記錄掃描次數(shù)和回表次數(shù),所以,在建立前綴索引的時(shí)候,我們需要在占用空間和搜索效率之間做一個(gè)權(quán)衡

第二個(gè),使用前綴索引其實(shí)就沒法用覆蓋索引對(duì)查詢性能的優(yōu)化了,因?yàn)?InnoDB 并不能確定前綴索引的定義是否截?cái)嗔送暾畔ⅲ退闶峭耆戎辛饲熬Y索引,InnoDB 還得回表確認(rèn)一次到底是不是滿足條件了。

 

責(zé)任編輯:武曉燕 來源: 飛天小牛肉
相關(guān)推薦

2011-07-08 09:43:51

Chromebook

2011-06-17 14:24:28

Java

2011-06-01 10:09:49

Ubuntu

2023-05-08 10:54:39

IT管理CIO

2021-03-01 18:48:21

Go管理工具

2018-03-20 13:56:12

數(shù)據(jù)庫MySQL枚舉類型

2024-06-19 19:28:51

2011-02-21 09:04:25

2015-10-21 14:27:18

ORACLE 超長(zhǎng)字符解決辦法

2018-02-05 23:14:35

光纖網(wǎng)絡(luò)光纖施工

2012-04-29 10:40:23

APP

2021-09-10 13:23:57

數(shù)據(jù)閃存 磁盤

2013-01-17 17:14:52

Objective-C

2013-05-10 10:49:53

2014-01-13 09:35:13

創(chuàng)業(yè)企業(yè)

2015-09-15 13:22:08

數(shù)據(jù)分析七宗罪

2010-11-26 10:29:21

MySQL批量替換

2018-12-19 15:30:27

Python編程語言

2009-03-13 13:52:56

2011-02-23 10:51:36

Chrome
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)