1. 摘要
MySQL用來加快查詢的技術(shù)很多债朵,其中最重要的是索引隐砸。通常索引能夠快速提高查詢速度。如果不適用索引锄奢,MYSQL必須從第一條記錄開始然后讀完整個表直到找出相關(guān)的行失晴。表越大,花費的時間越多拘央。但也不全是這樣涂屁。本文討論索引是什么以及如何使用索引來改善性能,以及索引可能降低性能的情況灰伟。
2.MySQL索引原理
索引目的
索引的目的在于提高查詢效率拆又,可以類比字典,如果要查“mysql”這個單詞栏账,我們肯定需要定位到m字母帖族,然后從下往下找到y(tǒng)字母,再找到剩下的sql挡爵。如果沒有索引竖般,那么你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢茶鹃?或者ze開頭的單詞呢涣雕?是不是覺得如果沒有索引,這個事情根本無法完成闭翩?
索引原理
除了詞典挣郭,生活中隨處可見索引的例子,如火車站的車次表疗韵、圖書的目錄等兑障。它們的原理都是一樣的,通過不斷的縮小想要獲得數(shù)據(jù)的范圍來篩選出最終想要的結(jié)果蕉汪,同時把隨機的事件變成順序的事件旺垒,也就是我們總是通過同一種查找方式來鎖定數(shù)據(jù)。
數(shù)據(jù)庫也是一樣肤无,但顯然要復(fù)雜許多,因為不僅面臨著等值查詢骇钦,還有范圍查詢(>宛渐、<、between眯搭、in)窥翩、模糊查詢(like)、并集查詢(or)等等鳞仙。數(shù)據(jù)庫應(yīng)該選擇怎么樣的方式來應(yīng)對所有的問題呢寇蚊?我們回想字典的例子,能不能把數(shù)據(jù)分成段棍好,然后分段查詢呢仗岸?最簡單的如果1000條數(shù)據(jù)允耿,1到100分成第一段,101到200分成第二段扒怖,201到300分成第三段……這樣查第250條數(shù)據(jù)较锡,只要找第三段就可以了,一下子去除了90%的無效數(shù)據(jù)盗痒。但如果是1千萬的記錄呢蚂蕴,分成幾段比較好?稍有算法基礎(chǔ)的同學(xué)會想到搜索樹俯邓,其平均復(fù)雜度是lgN骡楼,具有不錯的查詢性能。但這里我們忽略了一個關(guān)鍵的問題稽鞭,復(fù)雜度模型是基于每次相同的操作成本來考慮的鸟整,數(shù)據(jù)庫實現(xiàn)比較復(fù)雜,數(shù)據(jù)保存在磁盤上川慌,而為了提高性能吃嘿,每次又可以把部分數(shù)據(jù)讀入內(nèi)存來計算,因為我們知道訪問磁盤的成本大概是訪問內(nèi)存的十萬倍左右梦重,所以簡單的搜索樹難以滿足復(fù)雜的應(yīng)用場景兑燥。
磁盤IO與預(yù)讀
前面提到了訪問磁盤,那么這里先簡單介紹一下磁盤IO和預(yù)讀琴拧,磁盤讀取數(shù)據(jù)靠的是機械運動降瞳,每次讀取數(shù)據(jù)花費的時間可以分為尋道時間、旋轉(zhuǎn)延遲蚓胸、傳輸時間三個部分挣饥,尋道時間指的是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下沛膳;旋轉(zhuǎn)延遲就是我們經(jīng)常聽說的磁盤轉(zhuǎn)速扔枫,比如一個磁盤7200轉(zhuǎn),表示每分鐘能轉(zhuǎn)7200次锹安,也就是說1秒鐘能轉(zhuǎn)120次短荐,旋轉(zhuǎn)延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或?qū)?shù)據(jù)寫入磁盤的時間叹哭,一般在零點幾毫秒忍宋,相對于前兩個時間可以忽略不計。那么訪問一次磁盤的時間风罩,即一次磁盤IO的時間約等于5+4.17 = 9ms左右糠排,聽起來還挺不錯的,但要知道一臺500 -MIPS的機器每秒可以執(zhí)行5億條指令超升,因為指令依靠的是電的性質(zhì)入宦,換句話說執(zhí)行一次IO的時間可以執(zhí)行40萬條指令哺徊,數(shù)據(jù)庫動輒十萬百萬乃至千萬級數(shù)據(jù),每次9毫秒的時間云石,顯然是個災(zāi)難唉工。下圖是計算機硬件延遲的對比圖,供大家參考:
various-system-software-hardware-latencies
考慮到磁盤IO是非常高昂的操作汹忠,計算機操作系統(tǒng)做了一些優(yōu)化淋硝,當(dāng)一次IO時,不光把當(dāng)前磁盤地址的數(shù)據(jù)宽菜,而是把相鄰的數(shù)據(jù)也都讀取到內(nèi)存緩沖區(qū)內(nèi)谣膳,因為局部預(yù)讀性原理告訴我們,當(dāng)計算機訪問一個地址的數(shù)據(jù)的時候铅乡,與其相鄰的數(shù)據(jù)也會很快被訪問到继谚。每一次IO讀取的數(shù)據(jù)我們稱之為一頁(page)。具體一頁有多大數(shù)據(jù)跟操作系統(tǒng)有關(guān)阵幸,一般為4k或8k花履,也就是我們讀取一頁內(nèi)的數(shù)據(jù)時候,實際上才發(fā)生了一次IO挚赊,這個理論對于索引的數(shù)據(jù)結(jié)構(gòu)設(shè)計非常有幫助诡壁。
索引的數(shù)據(jù)結(jié)構(gòu)
前面講了生活中索引的例子,索引的基本原理荠割,數(shù)據(jù)庫的復(fù)雜性妹卿,又講了操作系統(tǒng)的相關(guān)知識,目的就是讓大家了解蔑鹦,任何一種數(shù)據(jù)結(jié)構(gòu)都不是憑空產(chǎn)生的夺克,一定會有它的背景和使用場景,我們現(xiàn)在總結(jié)一下嚎朽,我們需要這種數(shù)據(jù)結(jié)構(gòu)能夠做些什么铺纽,其實很簡單,那就是:每次查找數(shù)據(jù)時把磁盤IO次數(shù)控制在一個很小的數(shù)量級哟忍,最好是常數(shù)數(shù)量級室囊。那么我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣魁索,b+樹應(yīng)運而生。
詳解b+樹
b+樹
如上圖盼铁,是一顆b+樹粗蔚,關(guān)于b+樹的定義可以參見B+樹,這里只說一些重點饶火,淺藍色的塊我們稱之為一個磁盤塊鹏控,可以看到每個磁盤塊包含幾個數(shù)據(jù)項(深藍色所示)和指針(黃色所示)致扯,如磁盤塊1包含數(shù)據(jù)項17和35,包含指針P1当辐、P2抖僵、P3潦牛,P1表示小于17的磁盤塊狗唉,P2表示在17和35之間的磁盤塊,P3表示大于35的磁盤塊畴栖。真實的數(shù)據(jù)存在于葉子節(jié)點即3找筝、5蹈垢、9、10袖裕、13曹抬、15、28急鳄、29谤民、36、60疾宏、75张足、79、90灾锯、99兢榨。非葉子節(jié)點只不存儲真實的數(shù)據(jù),只存儲指引搜索方向的數(shù)據(jù)項顺饮,如17吵聪、35并不真實存在于數(shù)據(jù)表中。
b+樹的查找過程
如圖所示兼雄,如果要查找數(shù)據(jù)項29吟逝,那么首先會把磁盤塊1由磁盤加載到內(nèi)存,此時發(fā)生一次IO赦肋,在內(nèi)存中用二分查找確定29在17和35之間块攒,鎖定磁盤塊1的P2指針,內(nèi)存時間因為非常短(相比磁盤的IO)可以忽略不計佃乘,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內(nèi)存囱井,發(fā)生第二次IO,29在26和30之間趣避,鎖定磁盤塊3的P2指針庞呕,通過指針加載磁盤塊8到內(nèi)存,發(fā)生第三次IO,同時內(nèi)存中做二分查找找到29住练,結(jié)束查詢地啰,總計三次IO。真實的情況是讲逛,3層的b+樹可以表示上百萬的數(shù)據(jù)亏吝,如果上百萬的數(shù)據(jù)查找只需要三次IO,性能提高將是巨大的盏混,如果沒有索引蔚鸥,每個數(shù)據(jù)項都要發(fā)生一次IO,那么總共需要百萬次的IO括饶,顯然成本非常非常高株茶。
b+樹性質(zhì)
1.通過上面的分析,我們知道IO次數(shù)取決于b+數(shù)的高度h图焰,假設(shè)當(dāng)前數(shù)據(jù)表的數(shù)據(jù)為N启盛,每個磁盤塊的數(shù)據(jù)項的數(shù)量是m,則有h=㏒(m+1)N技羔,當(dāng)數(shù)據(jù)量N一定的情況下僵闯,m越大,h越刑倮摹鳖粟;而m = 磁盤塊的大小 / 數(shù)據(jù)項的大小,磁盤塊的大小也就是一個數(shù)據(jù)頁的大小拙绊,是固定的向图,如果數(shù)據(jù)項占的空間越小,數(shù)據(jù)項的數(shù)量越多标沪,樹的高度越低榄攀。這就是為什么每個數(shù)據(jù)項,即索引字段要盡量的小金句,比如int占4字節(jié)檩赢,要比bigint8字節(jié)少一半。這也是為什么b+樹要求把真實的數(shù)據(jù)放到葉子節(jié)點而不是內(nèi)層節(jié)點违寞,一旦放到內(nèi)層節(jié)點贞瞒,磁盤塊的數(shù)據(jù)項會大幅度下降,導(dǎo)致樹增高趁曼。當(dāng)數(shù)據(jù)項等于1時將會退化成線性表军浆。
2.當(dāng)b+樹的數(shù)據(jù)項是復(fù)合的數(shù)據(jù)結(jié)構(gòu),比如(name,age,sex)的時候挡闰,b+數(shù)是按照從左到右的順序來建立搜索樹的乒融,比如當(dāng)(張三,20,F)這樣的數(shù)據(jù)來檢索的時候,b+樹會優(yōu)先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex簇抵,最后得到檢索的數(shù)據(jù);但當(dāng)(20,F)這樣的沒有name的數(shù)據(jù)來的時候射众,b+樹就不知道下一步該查哪個節(jié)點碟摆,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據(jù)name來搜索才能知道下一步去哪里查詢叨橱。比如當(dāng)(張三,F)這樣的數(shù)據(jù)來檢索時典蜕,b+樹可以用name來指定搜索方向,但下一個字段age的缺失罗洗,所以只能把名字等于張三的數(shù)據(jù)都找到愉舔,然后再匹配性別是F的數(shù)據(jù)了, 這個是非常重要的性質(zhì)伙菜,即索引的最左匹配特性轩缤。
3. 索引分類和操作
索引的存儲分類
索引是在MYSQL的存儲引擎層中實現(xiàn)的,而不是在服務(wù)層實現(xiàn)的贩绕。所以每種存儲引擎的索引都不一定完全相同火的,也不是所有的存儲引擎都支持所有的索引類型。MYSQL目前提供了一下4種索引淑倾。
- B-Tree 索引:最常見的索引類型馏鹤,大部分引擎都支持B樹索引。
- HASH 索引:只有Memory引擎支持娇哆,使用場景簡單湃累。
- R-Tree 索引(空間索引):空間索引是MyISAM的一種特殊索引類型,主要用于地理空間數(shù)據(jù)類型碍讨。
- Full-text (全文索引):全文索引也是MyISAM的一種特殊索引類型治力,主要用于全文索引,InnoDB從MYSQL5.6版本提供對全文索引的支持垄开。
Mysql目前不支持函數(shù)索引琴许,但是能對列的前面某一部分進行索引,例如標(biāo)題title字段溉躲,可以只取title的前10個字符進行索引榜田,這個特性可以大大縮小索引文件的大小,但前綴索引也有缺點锻梳,在排序Order By和分組Group By 操作的時候無法使用箭券。用戶在設(shè)計表結(jié)構(gòu)的時候也可以對文本列根據(jù)此特性進行靈活設(shè)計。
語法:create index idx_title on film (title(10))
MyISAM疑枯、InnoDB引擎辩块、Memory三個常用引擎類型比較
索引 | MyISAM引擎 | InnoDB引擎 | Memory引擎 |
---|---|---|---|
B-Tree索引 | 支持 | 支持 | 支持 |
HASH 索引 | 不支持 | 不支持 | 支持 |
R-Tree 索引 | 支持 | 不支持 | 不支持 |
Full-text 索引 | 不支持 | 暫不支持 | 不支持 |
B-TREE索引類型
普通索引
這是最基本的索引類型,而且它沒有唯一性之類的限制。普通索引可以通過以下幾種方式創(chuàng)建:
(1)創(chuàng)建索引: CREATE INDEX 索引名 ON 表名(列名1废亭,列名2,...);
(2)修改表: ALTER TABLE 表名ADD INDEX 索引名 (列名1国章,列名2,...);
(3)創(chuàng)建表時指定索引:CREATE TABLE 表名 ( [...], INDEX 索引名 (列名1,列名 2,...) );
UNIQUE索引
表示唯一的豆村,不允許重復(fù)的索引液兽,如果該字段信息保證不會重復(fù)例如身份證號用作索引時,可設(shè)置為unique:
(1)創(chuàng)建索引:CREATE UNIQUE INDEX 索引名 ON 表名(列的列表);
(2)修改表:ALTER TABLE 表名ADD UNIQUE 索引名 (列的列表);
(3)創(chuàng)建表時指定索引:CREATE TABLE 表名( [...], UNIQUE 索引名 (列的列表) );
主鍵:PRIMARY KEY索引
主鍵是一種唯一性索引掌动,但它必須指定為“PRIMARY KEY”四啰。
(1)主鍵一般在創(chuàng)建表的時候指定:“CREATE TABLE 表名( [...], PRIMARY KEY (列的列表) ); ”。
(2)但是粗恢,我們也可以通過修改表的方式加入主鍵:“ALTER TABLE 表名ADD PRIMARY KEY (列的列表); ”柑晒。
每個表只能有一個主鍵。 (主鍵相當(dāng)于聚合索引眷射,是查找最快的索引)
注:不能用CREATE INDEX語句創(chuàng)建PRIMARY KEY索引
刪除索引
可利用ALTER TABLE或DROP INDEX語句來刪除索引匙赞。類似于CREATE INDEX語句,DROP INDEX可以在ALTER TABLE內(nèi)部作為一條語句處理凭迹,語法如下罚屋。
DROP INDEX index_name ON talbe_name
ALTER TABLE table_name DROP INDEX index_name
ALTER TABLE table_name DROP PRIMARY KEY
其中,前兩條語句是等價的嗅绸,刪除掉table_name中的索引index_name脾猛。
第3條語句只在刪除PRIMARY KEY索引時使用,因為一個表只可能有一個PRIMARY KEY索引鱼鸠,因此不需要指定索引名猛拴。如果沒有創(chuàng)建PRIMARY KEY索引,但表具有一個或多個UNIQUE索引蚀狰,則MySQL將刪除第一個UNIQUE索引愉昆。
如果從表中刪除了某列,則索引會受到影響麻蹋。對于多列組合的索引跛溉,如果刪除其中的某列,則該列也會從索引中刪除扮授。如果刪除組成索引的所有列芳室,則整個索引將被刪除。
查看索引
mysql> show index from tblname;
mysql> show keys from tblname;
Table:表的名稱
Non_unique:如果索引不能包括重復(fù)詞刹勃,則為0堪侯。如果可以,則為1
Key_name:索引的名稱
Seq_in_index:索引中的列序列號荔仁,從1開始
Column_name:列名稱
Collation:列以什么方式存儲在索引中伍宦。在MySQL中芽死,有值‘A’(升序)或NULL(無分類)。
Cardinality:索引中唯一值的數(shù)目的估計值次洼。通過運行ANALYZE TABLE或myisamchk -a可以更新关贵。基數(shù)根據(jù)被存儲為整數(shù)的統(tǒng)計數(shù)據(jù)來計數(shù)卖毁,所以即使對于小型表坪哄,該值也沒有必要是精確的∈拼郏基數(shù)越大,當(dāng)進行聯(lián)合時模暗,MySQL使用該索引的機會就越大禁悠。
Sub_part:如果列只是被部分地編入索引,則為被編入索引的字符的數(shù)目兑宇。如果整列被編入索引碍侦,則為NULL。
Packed:指示關(guān)鍵字如何被壓縮隶糕。如果沒有被壓縮瓷产,則為NULL。
Null:如果列含有NULL枚驻,則含有YES濒旦。如果沒有,則該列含有NO再登。
Index_type:用過的索引方法(BTREE, FULLTEXT, HASH, RTREE)尔邓。
Comment:更多評注。
聯(lián)合索引
聯(lián)合索引的定義為(MySQL):
ALTER TABLE `table_name` ADD INDEX (`col1`,`col2`,`col3`);
聯(lián)合索引的優(yōu)點:
若多個一條SQL锉矢,需要多個用到兩個條件
SELECT * FROM `user_info` WHERE username='XX',password='XXXXXX';
當(dāng)索引在檢索 password字段的時候梯嗽,數(shù)據(jù)量大大縮小,索引的命中率減小沽损,增大了索引的效率灯节。
符合索引的索引體積比單獨索引的體積要小,而且只是一個索引樹绵估,相比單獨列的索引要更加的節(jié)省時間復(fù)雜度和空間復(fù)雜度炎疆。
例如,有1000W條數(shù)據(jù)的表壹士,有如下sql:select * from table where a = 1 and b =2 and c = 3,假設(shè)假設(shè)每個條件可以篩選出10%的數(shù)據(jù)磷雇,如果只有單值索引,那么通過該索引能篩選出1000W*10%=100w 條數(shù)據(jù)躏救,然后再回表從100w條數(shù)據(jù)中找到符合b=2 and c= 3的數(shù)據(jù)唯笙,然后再排序螟蒸,再分頁。
如果是復(fù)合索引崩掘,通過索引篩選出1000w *10% *10% *10%=1w七嫌,然后再排序、分頁苞慢,哪個更高效诵原,一眼便知。
聯(lián)合索引命中的本質(zhì)(最左匹配原則)
在Mysql建立多列索引(聯(lián)合索引)有最左前綴的原則挽放,即最左優(yōu)先绍赛。
如果我們建立了一個2列的聯(lián)合索引(col1,col2),實際上已經(jīng)建立了兩個聯(lián)合索引(col1)、(col1,col2);
如果有一個3列索引(col1,col2,col3)辑畦,實際上已經(jīng)建立了三個聯(lián)合索引(col1)吗蚌、(col1,col2)、(col1,col2,col3)纯出。
創(chuàng)建聯(lián)合索引時列的選擇原則
經(jīng)常用的列優(yōu)先(最左匹配原則)
離散度高的列優(yōu)先(離散度高原則)
寬度小的列優(yōu)先(最少空間原則)
索引選擇注意事項
既然索引可以加快查詢速度蚯妇,那么是不是只要是查詢語句需要,就建上索引暂筝?答案是否定的箩言。因為索引雖然加快了查詢速度,但索引也是有代價的:索引文件本身要消耗存儲空間焕襟,同時索引會加重插入陨收、刪除和修改記錄時的負擔(dān),另外鸵赖,MySQL在運行時也要消耗資源維護索引畏吓,因此索引并不是越多越好。
一般兩種情況下不建議建索引:
表記錄比較少卫漫,例如一兩千條甚至只有幾百條記錄的表菲饼,沒必要建索引,讓查詢做全表掃描就好了;
至于多少條記錄才算多列赎,這個個人有個人的看法宏悦,我個人的經(jīng)驗是以2000作為分界線,記錄數(shù)不超過 2000可以考慮不建索引包吝,超過2000條可以酌情考慮索引饼煞。
索引的選擇性較低。所謂索引的選擇性(Selectivity)诗越,是指不重復(fù)的索引值(也叫基數(shù)砖瞧,Cardinality)與表記錄數(shù)(#T)的比值:
Index Selectivity = Cardinality / #T
顯然選擇性的取值范圍為(0, 1],選擇性越高的索引價值越大嚷狞,這是由B+Tree的性質(zhì)決定的块促。例如荣堰,上文用到的employees.titles表,如果title字段經(jīng)常被單獨查詢竭翠,是否需要建索引振坚,我們看一下它的選擇性:
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
+-------------+
| Selectivity |
+-------------+
| 0.0000 |
+-------------+
title的選擇性不足0.0001(精確值為0.00001579),所以實在沒有什么必要為其單獨建索引斋扰。
MySQL只對一下操作符才使用索引:<,<=,=,>,>=,between,in, 以及某些時候的like(不以通配符%或_開頭的情形)渡八。
不要過度索引,只保持所需的索引传货。每個額外的索引都要占用額外的磁盤空間屎鳍,并降低寫操作的性能。 在修改表的內(nèi)容時问裕,索引必須進行更新哥艇,有時可能需要重構(gòu),因此僻澎,索引越多,所花的時間越長十饥。
4. 慢查詢優(yōu)化
4.1 MySQL Explain詳解
我們常常用到explain這個命令來查看一個這些SQL語句的執(zhí)行計劃窟勃,查看該SQL語句有沒有使用上了索引,有沒有做全表掃描逗堵,這都可以通過explain命令來查看秉氧。所以我們深入了解MySQL的基于開銷的優(yōu)化器,還可以獲得很多可能被優(yōu)化器考慮到的訪問策略的細節(jié)蜒秤,以及當(dāng)運行SQL語句時哪種策略預(yù)計會被優(yōu)化器采用汁咏。
-- 實際SQL,查找用戶名為Jefabc的員工
select * from emp where name = 'Jefabc';
-- 查看SQL是否使用索引作媚,前面加上explain即可
explain select * from emp where name = 'Jefabc';
explain查詢結(jié)果:expain出來的信息有10列攘滩,分別是id、select_type纸泡、table漂问、type、possible_keys女揭、key蚤假、key_len、ref吧兔、rows磷仰、Extra
概要描述:
id:選擇標(biāo)識符
select_type:表示查詢的類型。
table:輸出結(jié)果集的表
partitions:匹配的分區(qū)
type:表示表的連接類型
possible_keys:表示查詢時境蔼,可能使用的索引
key:表示實際使用的索引
key_len:索引字段的長度
ref:列與索引的比較
rows:掃描出的行數(shù)(估算的行數(shù))
filtered:按表條件過濾的行百分比
Extra:執(zhí)行情況的描述和說明
id
SELECT識別符灶平。這是SELECT的查詢序列號
我的理解是SQL執(zhí)行的順序的標(biāo)識伺通,SQL從大到小的執(zhí)行
1. id相同時,執(zhí)行順序由上至下
2. 如果是子查詢民逼,id的序號會遞增泵殴,id值越大優(yōu)先級越高,越先被執(zhí)行
3. id如果相同拼苍,可以認為是一組笑诅,從上往下順序執(zhí)行;在所有組中疮鲫,id值越大吆你,優(yōu)先級越高,越先執(zhí)行
-- 查看在研發(fā)部并且名字以Jef開頭的員工俊犯,經(jīng)典查詢
explain select e.no, e.name from emp e left join dept d on e.dept_no = d.no where e.name like 'Jef%' and d.name = '研發(fā)部';
select_type
示查詢中每個select子句的類型:
(1) SIMPLE(簡單SELECT妇多,不使用UNION或子查詢等)
(2) PRIMARY(子查詢中最外層查詢,查詢中若包含任何復(fù)雜的子部分燕侠,最外層的select被標(biāo)記為PRIMARY)
(3) UNION(UNION中的第二個或后面的SELECT語句)
(4) DEPENDENT UNION(UNION中的第二個或后面的SELECT語句者祖,取決于外面的查詢)
(5) UNION RESULT(UNION的結(jié)果,union語句中第二個select開始后面所有select)
(6) SUBQUERY(子查詢中的第一個SELECT绢彤,結(jié)果不依賴于外部查詢)
(7) DEPENDENT SUBQUERY(子查詢中的第一個SELECT七问,依賴于外部查詢)
(8) DERIVED(派生表的SELECT, FROM子句的子查詢)
(9) UNCACHEABLE SUBQUERY(一個子查詢的結(jié)果不能被緩存,必須重新評估外鏈接的第一行)
table
顯示這一步所訪問數(shù)據(jù)庫中表名稱(顯示這一行的數(shù)據(jù)是關(guān)于哪張表的)茫舶,有時不是真實的表名字械巡,可能是簡稱,例如上面的e饶氏,d讥耗,也可能是第幾步執(zhí)行的結(jié)果的簡稱
type
對表訪問方式,表示MySQL在表中找到所需行的方式疹启,又稱“訪問類型”古程。
常用的類型有:ALL、index喊崖、range籍琳、 ref、eq_ref贷祈、const趋急、system、NULL(從左到右势誊,性能從差到好)
- ALL:Full Table Scan呜达, MySQL將遍歷全表以找到匹配的行
- index: Full Index Scan,index與ALL區(qū)別為index類型只遍歷索引樹
- range:只檢索給定范圍的行粟耻,使用一個索引來選擇行
- ref: 表示上述表的連接匹配條件查近,即哪些列或常量被用于查找索引列上的值
- eq_ref: 類似ref眉踱,區(qū)別就在使用的索引是唯一索引,對于每個索引鍵值霜威,表中只有一條記錄匹配谈喳,簡單來說,就是多表連接中使用primary key或者 unique key作為關(guān)聯(lián)條件
- const戈泼、system: 當(dāng)MySQL對查詢某部分進行優(yōu)化婿禽,并轉(zhuǎn)換為一個常量時,使用這些類型訪問大猛。如將主鍵置于where列表中扭倾,MySQL就能將該查詢轉(zhuǎn)換為一個常量,system是const類型的特例挽绩,當(dāng)查詢的表只有一行的情況下膛壹,使用system
- NULL: MySQL在優(yōu)化過程中分解語句,執(zhí)行時甚至不用訪問表或索引唉堪,例如從一個索引列里選取最小值可以通過單獨索引查找完成模聋。
possible_keys
指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引唠亚,則該索引將被列出链方,但不一定被查詢使用(該查詢可以利用的索引,如果沒有任何索引顯示 null)趾撵。
該列完全獨立于EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用共啃。
如果該列是NULL占调,則沒有相關(guān)的索引。在這種情況下移剪,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢性能究珊。如果是這樣,創(chuàng)造一個適當(dāng)?shù)乃饕⑶以俅斡肊XPLAIN檢查查詢
Key
key列顯示MySQL實際決定使用的鍵(索引)纵苛,必然包含在possible_keys中剿涮。
如果沒有選擇索引,鍵是NULL攻人。要想強制MySQL使用或忽視possible_keys列中的索引取试,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX怀吻。
key_len
表示索引中使用的字節(jié)數(shù)瞬浓,可通過該列計算查詢中使用的索引的長度(key_len顯示的值為索引字段的最大可能長度,并非實際使用長度蓬坡,即key_len是根據(jù)表定義計算而得猿棉,不是通過表內(nèi)檢索出的)磅叛。
不損失精確性的情況下,長度越短越好 萨赁。
ref
列與索引的比較弊琴,表示上述表的連接匹配條件,即哪些列或常量被用于查找索引列上的值杖爽。
rows
估算出結(jié)果集行數(shù)敲董,表示MySQL根據(jù)表統(tǒng)計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數(shù)
Extra
該列包含MySQL解決查詢的詳細信息,有以下幾種情況:
Using where:不用讀取表中所有信息掂林,僅通過索引就可以獲取所需數(shù)據(jù)臣缀,這發(fā)生在對表的全部的請求列都是同一個索引的部分的時候,表示mysql服務(wù)器將在存儲引擎檢索行后再進行過濾
Using temporary:表示MySQL需要使用臨時表來存儲結(jié)果集泻帮,常見于排序和分組查詢精置,常見 group by ; order by
Using filesort:當(dāng)Query中包含 order by 操作,而且無法利用索引完成的排序操作稱為“文件排序”
-- 測試Extra的filesort
explain select * from emp order by name;
Using join buffer:改值強調(diào)了在獲取連接條件時沒有使用索引锣杂,并且需要連接緩沖區(qū)來存儲中間結(jié)果脂倦。如果出現(xiàn)了這個值,那應(yīng)該注意元莫,根據(jù)查詢的具體情況可能需要添加索引來改進能赖阻。
Impossible where:這個值強調(diào)了where語句會導(dǎo)致沒有符合條件的行(通過收集統(tǒng)計信息不可能存在結(jié)果)。
Select tables optimized away:這個值意味著僅通過使用索引踱蠢,優(yōu)化器可能僅從聚合函數(shù)結(jié)果中返回一行
No tables used:Query語句中使用from dual 或不含任何from子句
4.2 慢查詢優(yōu)化案例
4.2.1 復(fù)雜的深分頁問題優(yōu)化
背景
有一個article表火欧,用于存儲文章的基本信息的,有文章id茎截,作者id等一些屬性苇侵,有一個content表,主要用于存儲文章的內(nèi)容企锌,主鍵是article_id榆浓,需求需要將一些滿足條件的作者發(fā)布的文章導(dǎo)入到另外一個庫,所以我同事就在項目中先查詢出了符合條件的作者id撕攒,然后開啟了多個線程陡鹃,每個線程每次取一個作者id宴抚,執(zhí)行查詢和導(dǎo)入工作宁仔。
查詢出作者id是1111匀伏,名下的所有文章信息纤怒,文章內(nèi)容相關(guān)的信息的SQL如下:
SELECT
a.*, c.*
FROM
article a
LEFT JOIN content c ON a.id = c.article_id
WHERE
a.author_id = 1111
AND a.create_time < '2020-04-29 00:00:00'
LIMIT 210000,100
因為查詢的這個數(shù)據(jù)庫是機械硬盤的孵户,在offset查詢到20萬時佣蓉,查詢時間已經(jīng)特別長了秘症,運維同事那邊直接收到報警匪凉,說這個庫已經(jīng)IO阻塞了,已經(jīng)多次進行主從切換了蹬叭,我們就去navicat里面試著執(zhí)行了一下這個語句藕咏,也是一直在等待, 然后對數(shù)據(jù)庫執(zhí)行show proceesslist 命令查看了一下秽五,發(fā)現(xiàn)每個查詢都是處于Writing to net的狀態(tài)孽查,沒辦法只能先把導(dǎo)入的項目暫時下線,然后執(zhí)行kill命令將當(dāng)前的查詢都殺死進程(因為只是客戶端Stop的話坦喘,MySQL服務(wù)端會繼續(xù)查詢)盲再。
然后我們開始分析這條命令執(zhí)行慢的原因:
是否是聯(lián)合索引的問題
當(dāng)前是索引情況如下:
article表的主鍵是id,author_id是一個普通索引
content表的主鍵是article_id
所以認為當(dāng)前是執(zhí)行流程是先去article表的普通索引author_id里面找到1111的所有文章id瓣铣,然后根據(jù)這些文章id去article表的聚集索引中找到所有的文章答朋,然后拿每個文章id去content表中找文章內(nèi)容等信息,然后判斷create_time是否滿足要求棠笑,進行過濾梦碗,最終找到offset為20000后的100條數(shù)據(jù)。
所以我們就將article的author_id索引改成了聯(lián)合索引(author_id,create_time),這樣聯(lián)合索引(author_id,create_time)中的B+樹就是先安裝author_id排序蓖救,再按照create_time排序洪规,這樣一開始在聯(lián)合(author_id,create_time)查詢出來的文章id就是滿足create_time < '2020-04-29 00:00:00'條件的,后面就不用進行過濾了循捺,就不會就是符合就不用對create_time過濾斩例。
流程確實是這個流程,但是去查詢時从橘,如果limit還是210000, 100時念赶,還是查不出數(shù)據(jù),幾分鐘都沒有數(shù)據(jù)恰力,一直到navica提示超時叉谜,使用Explain看的話,確實命中索引了牺勾,如果將offset調(diào)小正罢,調(diào)成6000, 100阵漏,勉強可以查出數(shù)據(jù)驻民,但是需要46s,所以瓶頸不在這里履怯。
真實原因如下:
先看關(guān)于深分頁的兩個查詢回还,id是主鍵,val是普通索引
直接查詢法
select * from test where val=4 limit 300000,5;
先查主鍵再join
select * from test a
inner join
(select id from test where val=4 limit 300000,5) as b
on a.id=b.id;
這兩個查詢的結(jié)果都是查詢出offset是30000后的5條數(shù)據(jù)叹洲,區(qū)別在于第一個查詢需要先去普通索引val中查詢出300005個id柠硕,然后去聚集索引下讀取300005個數(shù)據(jù)頁,然后拋棄前面的300000個結(jié)果,只返回最后5個結(jié)果蝗柔,過程中會產(chǎn)生了大量的隨機I/O闻葵。第二個查詢一開始在普通索引val下就只會讀取后5個id,然后去聚集索引下讀取5個數(shù)據(jù)頁癣丧。
同理我們業(yè)務(wù)中那條查詢其實是更加復(fù)雜的情況槽畔,因為我們業(yè)務(wù)的那條SQL不僅會讀取article表中的210100條結(jié)果,而且會每條結(jié)果去content表中查詢文章相關(guān)內(nèi)容胁编,而這張表有幾個TEXT類型的字段厢钧,我們使用show table status命令查看表相關(guān)的信息發(fā)現(xiàn)
Name | Engine | Row_format | Rows | Avg_Row_length |
---|---|---|---|---|
article | InnoDB | Compact | 2682682 | 266 |
content | InnoDB | Compact | 2824768 | 16847 |
發(fā)現(xiàn)兩個表的數(shù)據(jù)量都是200多萬的量級,article表的行平均長度是266嬉橙,content表的平均長度是16847早直,簡單來說是當(dāng) InnoDB 使用 Compact 或者 Redundant 格式存儲極長的 VARCHAR 或者 BLOB 這類大對象時,我們并不會直接將所有的內(nèi)容都存放在數(shù)據(jù)頁節(jié)點中市框,而是將行數(shù)據(jù)中的前 768 個字節(jié)存儲在數(shù)據(jù)頁中霞扬,后面會通過偏移量指向溢出頁。
(詳細了解可以看看這篇文章深度好文帶你讀懂MySQL和InnoDB)
這樣再從content表里面查詢連續(xù)的100行數(shù)據(jù)時拾给,讀取每行數(shù)據(jù)時祥得,還需要去讀溢出頁的數(shù)據(jù),這樣就需要大量隨機IO蒋得,因為機械硬盤的硬件特性级及,隨機IO會比順序IO慢很多。所以我們后來又進行了測試额衙,
只是從article表里面查詢limit 200000饮焦,100的數(shù)據(jù),發(fā)現(xiàn)即便存在深分頁的問題窍侧,查詢時間只是0.5s县踢,因為article表的平均列長度是266,所有數(shù)據(jù)都存在數(shù)據(jù)頁節(jié)點中伟件,不存在頁溢出硼啤,所以都是順序IO,所以比較快斧账。
//查詢時間0.51s
SELECT a.* FROM article a
WHERE a.author_id = 1111
AND a.create_time < '2020-04-29 00:00:00'
LIMIT 200100, 100
相反的谴返,我們直接先找出100個article_id去content表里面查詢數(shù)據(jù),發(fā)現(xiàn)比較慢咧织,第一次查詢時需要3s左右(也就是這些id的文章內(nèi)容相關(guān)的信息都沒有過嗓袱,沒有緩存的情況),第二次查詢時因為這些溢出頁數(shù)據(jù)已經(jīng)加載到buffer pool习绢,所以大概0.04s渠抹。
SELECT SQL_NO_CACHE c.*
FROM article_content c
WHERE c.article_id in(100個article_id)
解決方案
所以針對這個問題的解決方案主要有兩種:
先查出主鍵id再inner join
非連續(xù)查詢的情況下,也就是我們在查第100頁的數(shù)據(jù)時,不一定查了第99頁梧却,也就是允許跳頁查詢的情況奇颠,那么就是使用先查主鍵再join這種方法對我們的業(yè)務(wù)SQL進行改寫成下面這樣,下查詢出210000, 100時主鍵id放航,作為臨時表temp_table大刊,將article表與temp_table表進行inner join,查詢出中文章相關(guān)的信息三椿,并且去left Join content表查詢文章內(nèi)容相關(guān)的信息缺菌。 第一次查詢大概1.11s,后面每次查詢大概0.15s
SELECT
a.*, c.*
FROM article a
INNER JOIN(
SELECT id FROM article a
WHERE a.author_id = 1111
AND a.create_time < '2020-04-29 00:00:00'
LIMIT 210000 ,
100
) as temp_table ON a.id = temp_table.id
LEFT JOIN content c ON a.id = c.article_id
優(yōu)化結(jié)果
優(yōu)化前伴郁,offset達到20萬的量級時蛋叼,查詢時間過長,一直到超時狈涮。
優(yōu)化后狐胎,offset達到20萬的量級時握巢,查詢時間為1.11s。
利用范圍查詢條件來限制取出的數(shù)據(jù)
這種方法的大致思路如下松却,假設(shè)要查詢test_table中offset為10000的后100條數(shù)據(jù)暴浦,假設(shè)我們事先已知第10000條數(shù)據(jù)的id歌焦,值為min_id_value
select * from test_table where id > min_id_value order by id limit 0
, 100砚哆,就是即利用條件id > min_id_value在掃描索引是跳過10000條記錄躁锁,然后取100條數(shù)據(jù)即可,這種處理方式的offset值便成為0了关炼,但此種方式有限制,必須知道offset對應(yīng)id,然后作為min_id_value社痛,增加id > min_id_value的條件來進行過濾蒜哀,如果是用于分頁查找的話撵儿,也就是必須知道上一頁的最大的id狐血,所以只能一頁一頁得查匈织,不能跳頁,但是因為我們的業(yè)務(wù)需求就是每次100條數(shù)據(jù)纳决,進行分批導(dǎo)數(shù)據(jù)乡小,所以我們這種場景是可以使用满钟。針對這種方法,我們的業(yè)務(wù)SQL改寫如下:
//先查出最大和最小的id
SELECT min(a.id) as min_id , max(a.id) as max_id
FROM article a
WHERE a.author_id = 1111
AND a.create_time < '2020-04-29 00:00:00'
//然后每次循環(huán)查找
while(min_id<max_id) {
SELECT a.*, c.* FROM article a LEFT JOIN content c ON a.id = c.article_id WHERE a.author_id = 1111 AND a.id > min_id LIMIT 100
//這100條數(shù)據(jù)導(dǎo)入完畢后苗分,將100條數(shù)據(jù)數(shù)據(jù)中最大的id賦值給min_id摔癣,以便導(dǎo)入下100條數(shù)據(jù)
}
優(yōu)化結(jié)果
優(yōu)化前择浊,offset達到20萬的量級時琢岩,查詢時間過長师脂,一直到超時。
優(yōu)化后啄育,offset達到20萬的量級時挑豌,由于知道第20萬條數(shù)據(jù)的id墩崩,查詢時間為0.34s鹦筹。
4.2.2 聯(lián)合索引問題優(yōu)化
聯(lián)合索引其實有兩個作用:
1.充分利用where條件盛龄,縮小范圍
例如我們需要查詢以下語句:
SELECT * FROM test WHERE a = 1 AND b = 2
如果對字段a建立單列索引余舶,對b建立單列索引,那么在查詢時赠制,只能選擇走索引a钟些,查詢所有a=1的主鍵id政恍,然后進行回表达传,在回表的過程中宪赶,在聚集索引中讀取每一行數(shù)據(jù)搂妻,然后過濾出b = 2結(jié)果集,或者走索引b邓厕,也是這樣的過程邑狸。
如果對a单雾,b建立了聯(lián)合索引(a,b),那么在查詢時硅堆,直接在聯(lián)合索引中先查到a=1的節(jié)點贿讹,然后根據(jù)b=2繼續(xù)往下查民褂,查出符合條件的結(jié)果集赊堪,進行回表哭廉。
2.避免回表(此時也叫覆蓋索引)
這種情況就是假如我們只查詢某幾個常用字段遵绰,例如查詢a和b如下:
SELECT a,b FROM test WHERE a = 1 AND b = 2
對字段a建立單列索引,對b建立單列索引就需要像上面所說的乌企,查到符合條件的主鍵id集合后需要去聚集索引下回表查詢逛犹,但是如果我們要查詢的字段本身在聯(lián)合索引中就都包含了虽画,那么就不用回表了码撰。
3.減少需要回表的數(shù)據(jù)的行數(shù)
這種情況就是假如我們需要查詢a>1并且b=2的數(shù)據(jù)
SELECT * FROM test WHERE a > 1 AND b = 2
如果建立的是單列索引a脖岛,那么在查詢時會在單列索引a中把a>1的主鍵id全部查找出來然后進行回表。
如果建立的是聯(lián)合索引(a,b),基于最左前綴匹配原則陨溅,因為a的查詢條件是一個范圍查找(=或者in之外的查詢條件都是范圍查找)门扇,這樣雖然在聯(lián)合索引中查詢時只能命中索引a的部分臼寄,b的部分命中不了吉拳,只能根據(jù)a>1進行查詢留攒,但是由于聯(lián)合索引中每個葉子節(jié)點包含b的信息嫉嘀,在查詢出所有a>1的主鍵id時吃沪,也會對b=2進行篩選票彪,這樣需要回表的主鍵id就只有a>1并且b=2這部分了降铸,所以回表的數(shù)據(jù)量會變小推掸。
我們業(yè)務(wù)中碰到的就是第3種情況谅畅,我們的業(yè)務(wù)SQL本來更加復(fù)雜,還會join其他表胜茧,但是由于優(yōu)化的瓶頸在于建立聯(lián)合索引呻顽,所以進行了一些簡化廊遍,下面是簡化后的SQL:
SELECT
a.id as article_id ,
a.title as title ,
a.author_id as author_id
from
article a
where
a.create_time between '2020-03-29 03:00:00.003'
and '2020-04-29 03:00:00.003'
and a.status = 1
我們的需求其實就是從article表中查詢出最近一個月喉前,status為1的文章被饿,我們本來就是針對create_time建了單列索引狭握,結(jié)果在慢查詢?nèi)罩局邪l(fā)現(xiàn)了這條語句论颅,查詢時間需要0.91s左右恃疯,所以開始嘗試著進行優(yōu)化今妄。
為了便于測試盾鳞,我們在表中分別對create_time建立了單列索引create_time腾仅,對(create_time,status)建立聯(lián)合索引idx_createTime_status套利。
強制使用idx_createTime進行查詢
SELECT
a.id as article_id ,
a.title as title ,
a.author_id as author_id
from
article a FORCE INDEX(idx_createTime)
where
a.create_time between '2020-03-22 03:00:00.003'
and '2020-04-22 03:00:00.003'
and a.status = 1
強制使用idx_createTime_status進行查詢(即使不強制也是會選擇這個索引)
SELECT
a.id as article_id ,
a.title as title ,
a.author_id as author_id
from
article a FORCE INDEX(idx_createTime_status)
where
a.create_time between '2020-03-22 03:00:00.003'
and '2020-04-22 03:00:00.003'
and a.status = 1
優(yōu)化結(jié)果:
優(yōu)化前使用idx_createTime單列索引验辞,查詢時間為0.91s
優(yōu)化前使用idx_createTime_status聯(lián)合索引受神,查詢時間為0.21s
EXPLAIN的結(jié)果如下:
id | type | key | key_len | rows | filtered | Extra |
---|---|---|---|---|---|---|
1 | range | idx_createTime | 4 | 311608 | 25.00 | Using index condition; Using where |
2 | range | idx_createTime_status | 6 | 310812 | 100.00 | Using index condition |
原理分析
先介紹一下EXPLAIN中Extra列的各種取值的含義
Using filesort
當(dāng)Query 中包含 ORDER BY 操作鼻听,而且無法利用索引完成排序操作的時候撑碴,MySQL Query Optimizer 不得不選擇相應(yīng)的排序算法來實現(xiàn)醉拓。數(shù)據(jù)較少時從內(nèi)存排序亿卤,否則從磁盤排序排吴。Explain不會顯示的告訴客戶端用哪種排序钻哩。
Using index
僅使用索引樹中的信息從表中檢索列信息街氢,而不需要進行附加搜索來讀取實際行(使用二級覆蓋索引即可獲取數(shù)據(jù))。 當(dāng)查詢僅使用作為單個索引的一部分的列時荣刑,可以使用此策略厉亏。
Using temporary
要解決查詢叶堆,MySQL需要創(chuàng)建一個臨時表來保存結(jié)果虱颗。 如果查詢包含不同列的GROUP BY和ORDER BY子句忘渔,則通常會發(fā)生這種情況畦粮。官方解釋:”為了解決查詢宣赔,MySQL需要創(chuàng)建一個臨時表來容納結(jié)果儒将。典型情況如查詢包含可以按不同情況列出列的GROUP BY和ORDER BY子句時钩蚊。很明顯就是通過where條件一次性檢索出來的結(jié)果集太大了砰逻,內(nèi)存放不下了蝠咆,只能通過加臨時表來輔助處理勺美。
Using where
表示當(dāng)where過濾條件中的字段無索引時赡茸,MySQL Sever層接收到存儲引擎(例如innodb)的結(jié)果集后占卧,根據(jù)where條件中的條件進行過濾华蜒。
Using index condition
Using index condition 會先條件過濾索引叭喜,過濾完索引后找到所有符合索引條件的數(shù)據(jù)行,隨后用 WHERE 子句中的其他條件去過濾這些數(shù)據(jù)行闪幽;
我們的實際案例中溉知,其實就是走單個索引idx_createTime時,只能從索引中查出 滿足a.create_time between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003'
條件的主鍵id舌劳,然后進行回表,因為idx_createTime索引中沒有status的信息材诽,只能回表后查出所有的主鍵id對應(yīng)的行脸侥。然后innodb將結(jié)果集返回給MySQL Sever睁枕,MySQL Sever根據(jù)status字段進行過濾外遇,篩選出status為1的字段跳仿,所以第一個查詢的Explain結(jié)果中的Extra才會顯示Using where菲语。
filtered字段表示存儲引擎返回的數(shù)據(jù)在server層過濾后山上,剩下多少滿足查詢的記錄數(shù)量的比例佩憾,這個是預(yù)估值妄帘,因為status取值是null寄摆,1桑阶,2蚣录,3萎河,4虐杯,所以這里給的25%擎椰。
所以第二個查詢與第一個查詢的區(qū)別主要在于一開始去idx_createTime_status查到的結(jié)果集就是滿足status是1的id达舒,所以去聚集索引下進行回表查詢時巩搏,掃描的行數(shù)會少很多(大概是2.7萬行與15萬行的區(qū)別),之后innodb返回給MySQL Server的數(shù)據(jù)就是滿足條件status是1的結(jié)果集(2.7萬行)丈甸,不用再進行篩選了尿褪,所以第二個查詢才會快這么多,時間是優(yōu)化前的23%得湘。(兩種查詢方式的EXPLAIN預(yù)估掃描行數(shù)都是30萬行左右是因為idx_createTime_status只命中了createTime杖玲,因為createTime不是查單個值,查的是范圍)
//查詢結(jié)果行數(shù)是15萬行左右
SELECT count(*) from article a
where a.post_time
between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003'
//查詢結(jié)果行數(shù)是2萬6行左右
SELECT count(*) from article a
where a.post_time
between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003'
and a.audit_status = 1
發(fā)散思考:如果將聯(lián)合索引(createTime淘正,status)改成(status摆马,createTime)會怎么樣?
where
a.create_time between '2020-03-22 03:00:00.003'
and '2020-04-22 03:00:00.003'
and a.status = 1
根據(jù)最左匹配的原則囤采,因為我們的where查詢條件是這樣,如果是(createTime惩淳,status)那么索引就只能用到createTime蕉毯,如果是(status乓搬,createTime),因為status是查詢單個值代虾,所以status进肯,createTime都可以命中,在(status棉磨,createTime)索引中掃描行數(shù)會減少江掩,但是由于(createTime,status)這個索引本身值包含createTime乘瓤,status环形,id三個字段的信息,數(shù)據(jù)量比較小衙傀,而一個數(shù)據(jù)頁是16k斟赚,可以存儲1000個以上的索引數(shù)據(jù)節(jié)點,而且是查詢到createTime后差油,進行的順序IO拗军,所以讀取比較快,總得的查詢時間兩者基本是一致蓄喇。下面是測試結(jié)果:
首先創(chuàng)建了(status发侵,createTime)名叫idx_status_createTime,
SELECT
a.id as article_id ,
a.title as title ,
a.author_id as author_id
from
article a FORCE INDEX(idx_status_createTime)
where
a.create_time between '2020-03-22 03:00:00.003'
and '2020-04-22 03:00:00.003'
and a.status = 1
查詢時間是0.21妆偏,跟第二種方式(createTime刃鳄,status)索引的查詢時間基本一致。
Explain結(jié)果對比:
id | type | key | key_len | rows | filtered | Extra |
---|---|---|---|---|---|---|
2 | range | idx_createTime_status | 6 | 310812 | 100.00 | Using index condition |
3 | range | idx_status_createTime | 6 | 52542 | 100.00 | Using index condition |
掃描行數(shù)確實會少一些钱骂,因為在idx_status_createTime的索引中叔锐,一開始根據(jù)status = 1排除掉了status取值為其他值的情況。
5. 參考
(1)MySQL索引原理及慢查詢優(yōu)化 https://tech.meituan.com/2014/06/30/mysql-index.html
(2)MySQL Explain詳解 https://www.cnblogs.com/tufujie/p/9413852.html
(3)MySQL慢查詢優(yōu)化(線上案例調(diào)優(yōu))https://www.cnblogs.com/notfound9/p/12928763.html