MySQL系列(5):還是索引

今天我們?cè)倮^續(xù)聊聊跟 MySQL 索引有關(guān)的概念。

在開始這篇文章之前,我們先來看一下這個(gè)問題:在下面這個(gè)表 T 中搬男,如果我執(zhí)行 select * from T where k between 3 and 5钞它,需要執(zhí)行幾次樹的搜索操作,會(huì)掃描多少行扶关?

下面是這個(gè)表的初始化語句阴汇。

mysql> create table T (
ID int primary key,
k int NOT NULL DEFAULT 0, 
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine=InnoDB;

insert into T values(100,1,'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');
圖 1 InnoDB 的索引組織結(jié)構(gòu)

現(xiàn)在,我們一起來看看這條 SQL 查詢語句的執(zhí)行流程:

  1. 在 k 索引樹上找到 k=3 的記錄节槐,取得 ID = 300搀庶;
  2. 再到 ID 索引樹查到 ID=300 對(duì)應(yīng)的 R3;
  3. 在 k 索引樹取下一個(gè)值 k=5铜异,取得 ID=500哥倔;
  4. 再回到 ID 索引樹查到 ID=500 對(duì)應(yīng)的 R4;
  5. 在 k 索引樹取下一個(gè)值 k=6揍庄,不滿足條件咆蒿,循環(huán)結(jié)束。

在這個(gè)過程中蚂子,回到主鍵索引樹搜索的過程沃测,我們稱為回表。可以看到食茎,這個(gè)查詢過程讀了 k 索引樹的 3 條記錄(步驟 1蒂破、3 和 5),回表了兩次(步驟 2 和 4)别渔。

在這個(gè)例子中寞蚌,由于查詢結(jié)果所需要的數(shù)據(jù)只在主鍵索引上有,所以不得不回表钠糊。那么挟秤,有沒有可能經(jīng)過索引優(yōu)化,避免回表過程呢抄伍?

覆蓋索引

如果執(zhí)行的語句是 select ID from T where k between 3 and 5艘刚,這時(shí)只需要查 ID 的值,而 ID 的值已經(jīng)在 k 索引樹上了截珍,因此可以直接提供查詢結(jié)果攀甚,不需要回表。也就是說岗喉,在這個(gè)查詢里面秋度,索引 k 已經(jīng)“覆蓋了”我們的查詢需求,我們稱為覆蓋索引钱床。

由于覆蓋索引可以減少樹的搜索次數(shù)荚斯,顯著提升查詢性能,所以使用覆蓋索引是一個(gè)常用的性能優(yōu)化手段。

基于上面覆蓋索引的說明事期,我們來討論一個(gè)問題:在一個(gè)市民信息表上滥壕,是否有必要將身份證號(hào)和名字建立聯(lián)合索引?

假設(shè)這個(gè)市民表的定義是這樣的:

CREATE TABLE `tuser` (
  `id` int(11) NOT NULL,
  `id_card` varchar(32) DEFAULT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `ismale` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id_card` (`id_card`),
  KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB

我們知道兽泣,身份證號(hào)是市民的唯一標(biāo)識(shí)绎橘。也就是說,如果有根據(jù)身份證號(hào)查詢市民信息的需求唠倦,我們只要在身份證號(hào)字段上建立索引就夠了称鳞。而再建立一個(gè)(身份證號(hào)、姓名)的聯(lián)合索引稠鼻,是不是浪費(fèi)空間胡岔?

如果現(xiàn)在有一個(gè)高頻請(qǐng)求,要根據(jù)市民的身份證號(hào)查詢他的姓名枷餐,這個(gè)聯(lián)合索引就有意義了靶瘸。它可以在這個(gè)高頻請(qǐng)求上用到覆蓋索引,不再需要回表查整行記錄毛肋,減少語句的執(zhí)行時(shí)間怨咪。

最左前綴原則

看到這里你一定有一個(gè)疑問,如果為每一種查詢都設(shè)計(jì)一個(gè)索引润匙,索引是不是太多了诗眨。如果我現(xiàn)在要按照市民的身份證號(hào)去查他的家庭地址呢?雖然這個(gè)查詢需求在業(yè)務(wù)中出現(xiàn)的概率不高孕讳,但總不能讓它走全表掃描吧匠楚?反過來說,單獨(dú)為一個(gè)不頻繁的請(qǐng)求創(chuàng)建一個(gè)(身份證號(hào)厂财,地址)的索引又感覺有點(diǎn)浪費(fèi)芋簿。應(yīng)該怎么做呢?

這里璃饱,我先和你說結(jié)論吧与斤。B+ 樹這種索引結(jié)構(gòu),可以利用索引的“最左前綴”荚恶,來定位記錄撩穿。

為了直觀地說明這個(gè)概念,我們用(name谒撼,age)這個(gè)聯(lián)合索引來分析食寡。

圖 2 (name,age)索引示意圖

可以看到廓潜,索引項(xiàng)是按照索引定義里面出現(xiàn)的字段順序排序的抵皱。

當(dāng)你的邏輯需求是查到所有名字是“張三”的人時(shí)善榛,可以快速定位到 ID4,然后向后遍歷得到所有需要的結(jié)果叨叙。

如果你要查的是所有名字第一個(gè)字是“張”的人,你的 SQL 語句的條件是"where name like ‘張 %’"堪澎。這時(shí)擂错,你也能夠用上這個(gè)索引,查找到第一個(gè)符合條件的記錄是 ID3樱蛤,然后向后遍歷钮呀,直到不滿足條件為止。

可以看到昨凡,不只是索引的全部定義爽醋,只要滿足最左前綴,就可以利用索引來加速檢索便脊。這個(gè)最左前綴可以是聯(lián)合索引的最左 N 個(gè)字段蚂四,也可以是字符串索引的最左 M 個(gè)字符。

基于上面對(duì)最左前綴索引的說明哪痰,我們來討論一個(gè)問題:在建立聯(lián)合索引的時(shí)候遂赠,如何安排索引內(nèi)的字段順序

這里我們的評(píng)估標(biāo)準(zhǔn)是晌杰,索引的復(fù)用能力跷睦。因?yàn)榭梢灾С肿钭笄熬Y,所以當(dāng)已經(jīng)有了 (a,b) 這個(gè)聯(lián)合索引后肋演,一般就不需要單獨(dú)在 a 上建立索引了抑诸。因此,第一原則是爹殊,如果通過調(diào)整順序蜕乡,可以少維護(hù)一個(gè)索引,那么這個(gè)順序往往就是需要優(yōu)先考慮采用的梗夸。

所以現(xiàn)在你知道了异希,這段開頭的問題里,我們要為高頻請(qǐng)求創(chuàng)建 (身份證號(hào)绒瘦,姓名)這個(gè)聯(lián)合索引称簿,并用這個(gè)索引支持“根據(jù)身份證號(hào)查詢地址”的需求。

那么惰帽,如果既有聯(lián)合查詢憨降,又有基于 a、b 各自的查詢呢该酗?查詢條件里面只有 b 的語句授药,是無法使用 (a,b) 這個(gè)聯(lián)合索引的士嚎,這時(shí)候你不得不維護(hù)另外一個(gè)索引,也就是說你需要同時(shí)維護(hù) (a,b)悔叽、(b) 這兩個(gè)索引莱衩。

索引下推

上一段我們說到滿足最左前綴原則的時(shí)候,最左前綴可以用于在索引中定位記錄娇澎。這時(shí)笨蚁,你可能要問,那些不符合最左前綴的部分趟庄,會(huì)怎么樣呢括细?

我們還是以市民表的聯(lián)合索引(name, age)為例。如果現(xiàn)在有一個(gè)需求:檢索出表中“名字第一個(gè)字是張戚啥,而且年齡是 10 歲的所有男孩”奋单。那么,SQL 語句是這么寫的:

mysql> select * from tuser where name like '張%' and age=10 and ismale=1;

你已經(jīng)知道了前綴索引規(guī)則猫十,所以這個(gè)語句在搜索索引樹的時(shí)候览濒,只能用 “張”,找到第一個(gè)滿足條件的記錄 ID3拖云。當(dāng)然匾七,這還不錯(cuò),總比全表掃描要好江兢。

然后呢昨忆?

當(dāng)然是判斷其他條件是否滿足。

在 MySQL 5.6 之前杉允,只能從 ID3 開始一個(gè)個(gè)回表邑贴。到主鍵索引上找出數(shù)據(jù)行,再對(duì)比字段值叔磷。

而 MySQL 5.6 引入的索引下推優(yōu)化(index condition pushdown)拢驾, 可以在索引遍歷過程中,對(duì)索引中包含的字段先做判斷改基,直接過濾掉不滿足條件的記錄繁疤,減少回表次數(shù)。

圖 3 無索引下推執(zhí)行流程
圖 4 索引下推執(zhí)行流程

在圖 3 和 4 這兩個(gè)圖里面秕狰,每一個(gè)虛線箭頭表示回表一次稠腊。

圖 3 中,在 (name,age) 索引里面我特意去掉了 age 的值鸣哀,這個(gè)過程 InnoDB 并不會(huì)去看 age 的值架忌,只是按順序把“name 第一個(gè)字是’張’”的記錄一條條取出來回表。因此我衬,需要回表 4 次叹放。

圖 4 跟圖 3 的區(qū)別是饰恕,InnoDB 在 (name,age) 索引內(nèi)部就判斷了 age 是否等于 10,對(duì)于不等于 10 的記錄井仰,直接判斷并跳過埋嵌。在我們的這個(gè)例子中,只需要對(duì) ID4俱恶、ID5 這兩條記錄回表取數(shù)據(jù)判斷雹嗦,就只需要回表 2 次。

小結(jié)

今天這篇文章速那,我和你繼續(xù)討論了數(shù)據(jù)庫索引的概念俐银,包括了覆蓋索引尿背、前綴索引端仰、索引下推。你可以看到田藐,在滿足語句需求的情況下荔烧, 盡量少地訪問資源是數(shù)據(jù)庫設(shè)計(jì)的重要原則之一。我們?cè)谑褂脭?shù)據(jù)庫的時(shí)候汽久,尤其是在設(shè)計(jì)表結(jié)構(gòu)時(shí)鹤竭,也要以減少資源消耗作為目標(biāo)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末景醇,一起剝皮案震驚了整個(gè)濱河市臀稚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌三痰,老刑警劉巖吧寺,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異散劫,居然都是意外死亡稚机,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門获搏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赖条,“玉大人,你說我怎么就攤上這事常熙∥痴В” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵裸卫,是天一觀的道長(zhǎng)蕾额。 經(jīng)常有香客問我,道長(zhǎng)彼城,這世上最難降的妖魔是什么诅蝶? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任退个,我火速辦了婚禮,結(jié)果婚禮上调炬,老公的妹妹穿的比我還像新娘语盈。我一直安慰自己,他們只是感情好缰泡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布刀荒。 她就那樣靜靜地躺著,像睡著了一般棘钞。 火紅的嫁衣襯著肌膚如雪缠借。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天宜猜,我揣著相機(jī)與錄音泼返,去河邊找鬼。 笑死姨拥,一個(gè)胖子當(dāng)著我的面吹牛绅喉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叫乌,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柴罐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了憨奸?” 一聲冷哼從身側(cè)響起革屠,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎排宰,沒想到半個(gè)月后似芝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡额各,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年国觉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虾啦。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡麻诀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傲醉,到底是詐尸還是另有隱情蝇闭,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布硬毕,位于F島的核電站呻引,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吐咳。R本人自食惡果不足惜逻悠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一元践、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧童谒,春花似錦单旁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至琅豆,卻和暖如春愉豺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茫因。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蚪拦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人节腐。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓外盯,卻偏偏與公主長(zhǎng)得像摘盆,于是被迫代替她去往敵國和親翼雀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354