正文:
一、先說說什么是索引?
索引(index)翻譯為一個(gè)目錄,用于快速定位我們想要找的數(shù)據(jù)的位置。例如:我們把一個(gè)數(shù)據(jù)庫比作一本書,而索引(index)就是書中的目錄,此刻要找到書的某個(gè)感興趣的內(nèi)容,我們一般是不會(huì)整本書翻完再去確認(rèn)該內(nèi)容在哪里,而是通過書的目錄,定位到該內(nèi)容章節(jié)所在頁數(shù),最后直接翻到該頁面
我們來看看在數(shù)據(jù)庫中的索引:
全表掃描 VS 索引掃描
以字典為例,全表掃描就是如果我們查找某個(gè)字時(shí),那么通讀一遍新華字典,然后找到我們想要找到的字
而跟全表掃描相對(duì)應(yīng)的就是索引查找,索引查找就是在表的索引部分找到我們想要找的數(shù)據(jù)具體位置,然后會(huì)到表里面將我們想要找的數(shù)據(jù)全部查出
實(shí)例:在一張學(xué)生表找到一個(gè)名字叫Dev的學(xué)生
左邊全表掃描:需要從第一行開始一行行的掃描,直到找到100008行Dev這個(gè)學(xué)生的信息為止,將這個(gè)數(shù)據(jù)返回回來,但有可能該表中還有同名的學(xué)生,因此掃描并沒有結(jié)束,通常全表掃描要找到一個(gè)數(shù)據(jù),是需要將整張表的數(shù)據(jù)遍歷一遍,然后才能確定是否將所有數(shù)據(jù)返回
右邊索引掃描:索引查找是根據(jù)首字母排序找到D開頭的Dev,如果首字母相同,那么再根據(jù)第二個(gè)字母排序找到,以此類推,我們找到ID為100008,然后回表查出ID為100008的數(shù)據(jù)
結(jié)論:因此索引(對(duì)應(yīng)InnoDB)的索引值對(duì)應(yīng)的是主鍵ID
二、如何找到索引對(duì)應(yīng)的值
InnoDB引擎主要根據(jù)
(1)B+tree
(2)二分查找法
B+tree: B+樹擁有整棵樹的根節(jié)點(diǎn)、支節(jié)點(diǎn)和頁節(jié)點(diǎn),上層會(huì)存儲(chǔ)下層節(jié)點(diǎn)的管理范圍,直到頁節(jié)點(diǎn)的具體信息
二分查找法:根據(jù)B+樹存儲(chǔ)的各個(gè)節(jié)點(diǎn)的范圍,進(jìn)行比較,逐步縮小范圍,最后定位到頁節(jié)點(diǎn)中我們想要的位置
三、介紹下InnoDB表也是一張索引表
如上圖InnoDB表是聚簇表,意思是InnoDB本身是一張大的索引組織表,也是一個(gè)根據(jù)主鍵排序的大索引的B+樹結(jié)構(gòu),我們?cè)贗nnoDB里面另外建立自己想要索引的表的字段
聚簇索引就意味著InnoDB表本身,而我們把這些根據(jù)其他字段排序的索引稱為二級(jí)索引(secondery class)
四、在數(shù)據(jù)庫中如何建立索引
在MySQL中主要建立兩種類型的索引
1.單列索引
create?index?idx_name?on?tb_student(name); ??????????????索引名????????表名???字段名
2.聯(lián)合索引
create?index?idx_name_age?on?tb_student(name,age); #索引中先根據(jù)name排序,name相同的情況下根據(jù)age排序
五、索引維護(hù)
首先介紹下什么是索引維護(hù)?這是一個(gè)關(guān)乎性能的重要概念
如果索引所在字段發(fā)生了修改、刪除、插入等操作,那么索引項(xiàng)就會(huì)發(fā)生變化,因此如果不能保證索引的有序,那么就不能索引的準(zhǔn)確與效率,而索引的排序發(fā)生了變化的這個(gè)行為,我們稱為索引維護(hù) 在insert/delete/update操作時(shí),為了維護(hù)索引的排序,數(shù)據(jù)庫會(huì)自動(dòng)的完成索引項(xiàng)的維護(hù),索引的排序,這些行為對(duì)用戶是透明的,感覺不到的 在一個(gè)有索引的表中,創(chuàng)建它時(shí),實(shí)際上還同時(shí)創(chuàng)建了索引排序的表,因此在DML中,插入等操作不再是普通的插入,MySQL將它封裝成了一個(gè)事務(wù),連著索引項(xiàng)的排序表一起操作 因此,我們應(yīng)當(dāng)嚴(yán)格控制表上的索引數(shù)量,否則容易影響數(shù)據(jù)庫的性能
總結(jié)索引維護(hù)如下:
1、索引維護(hù)由數(shù)據(jù)庫自動(dòng)完成
2、插入/修改/刪除每一個(gè)索引行都變成一個(gè)內(nèi)部封裝的事務(wù)
3、索引越多,事務(wù)越大,代價(jià)越高
4、索引越多,對(duì)表的插入和索引字段的修改就越慢
因此可以看出索引并非是越多越好,在工作中也要慎用,尤其對(duì)于寫操作較為頻繁的業(yè)務(wù)
六、如何正確的使用索引?
1、依據(jù)where查詢條件建立索引
eg: select?a,b?from?tb_test?where?c?=??; idx_c(c)???->正確 select?a,b?from?tb_test?where?c?=???and?b?=?? idx_cd(c,d)??->正確
2、根據(jù)排序order by ,group by , distinct 字段添加索引
eg: select?*?from?tb_test?order?by?a; select?a,count(*)?from?tb_test?group?by?a; idx_a(a)??->正確 select?*?from?tb_test?order?by?a,b; idx_a_b(a,b)??->正確 select?*?from?tb_test?order?where?c?=???by?a; idx_c_a(c,a)??->正確
七、到底哪些字段適合創(chuàng)建索引?
1、字段值的重復(fù)程度,如圖:
身份證號(hào)碼由于基本上不可能重復(fù),因此選擇性非常好,而人的名字重復(fù)性較低,選擇性也不錯(cuò), 性別選擇性較差,重復(fù)度非常高
2、選擇性很差的字段通常不適合創(chuàng)建索引,但也有例外
?如:男女比例相仿的表中,性別不適合創(chuàng)建單列索引,如果走索引不如走全表掃描, ?因?yàn)樽咚饕腎/O開銷更大 ???? ?但如果男女比例極度不平衡,要查詢的又是少數(shù)方,如:理工學(xué)校、IT公司等可以考慮使用索引
3、聯(lián)合索引中選擇性好的字段應(yīng)該排在前面
select?*?from?tab_a?where?gender=??and?name=? idx_name_gender(name,gender)???->正確
4、聯(lián)合索引可以為單列、復(fù)列查詢提供幫助
idx_smp(a,b,c) where?a=?;????????????????->正確 where?a=??and?b=?;????????->正確 where?a=??and?c=?;????????->正確?(注:需要MySQL5.6版本以上;在5.5及以前版本,可以對(duì)a字段進(jìn)行索引掃描,但c字段不行????) where?a=??and?b=??and?c=??->正確
5、合理創(chuàng)建聯(lián)合索引,避免冗余
(a),(a,b),(a,b,c)??????->不可取 (a,b,c)????????????????->正確,可以覆蓋前兩個(gè)
八、再來看看如何在長字段上建立索引呢
首先,在較長的字段上建立索引是非常影響性能的,比如文章等超大varchar或者text字段,如果不是非建不可,一般不推薦,另外對(duì)InnoDB索引單字段(utf8)只能取前767bytes
那么如何處理長字段索引?
主要根據(jù)類型來分別處理: 1、Email類,可以建立前綴索引 mail_addr?varchar(2048) idx_mailadd(mail_addr(39))???->?正確 解析:由于email郵件類型字段,一般后綴都有較大可能相同,如.com?.cn等等,而前綴相同的可能性較低,且郵箱一般長度較短,因此可以建立前綴索引 2、住址類,分拆字段 home_addr?varchar(2048) idx_homeadd(home_addr(30))????->錯(cuò)誤,很可能前半段是相同的省市區(qū)街道名 province_add?varchar(1024),city_add?varchar(1024),?district_add?`varchar(1024),lolcal_add?varchar(1024)????--建立聯(lián)合索引或者單列索引?->正確`
九、對(duì)核心SQL索引做覆蓋掃描
對(duì)于最核心的SQL,我們可以考慮使用索引覆蓋,什么是索引覆蓋呢,下面是個(gè)例子
select?name?from?tb_user?where?userid=? key?idx_uid_name(userid,name)???->覆蓋索引掃描
我們查詢用戶名這種操作頻率非常高,而索引里面又存儲(chǔ)了字段的值,因此在我們做查詢時(shí),name字段的值直接在索引中返回,而不需要回表;還有一個(gè)使用非常廣泛的例子:用戶登陸,我們可以將username password做覆蓋索引,這樣大大提高登陸驗(yàn)證的速度
因此覆蓋索引覆蓋就是將你要查詢的字段和條件字段一起建立聯(lián)合索引,這樣的好處是不需要回表獲取name字段,IO最小,速度塊
十、哪些情況無法使用索引?
1、索引列進(jìn)行數(shù)據(jù)運(yùn)算或者函數(shù)運(yùn)算
eg: ???where?id+1=10;????->錯(cuò)誤,無法利用到索引 ???where?id=(10-1)???->正確 ???where?year(id)?錯(cuò)誤,無法利用到索引 ???where?col?正確
2、未含復(fù)合索引的前綴字段
idx_abc(a,b,c) where?b=??and?c=????->錯(cuò)誤,無法利用到索引 正確的建立索引方式(b,c)
3、前綴通配符”_” “%”等
like?'%ttt%'???->錯(cuò)誤,無法利用到索引 like?"ttt%"????->正確
4、where條件使用NOT,,!= 通常也無法使用到索引
5、字段類型不匹配
字段類型并不絕對(duì)匹配時(shí),可能會(huì)導(dǎo)致無法使用索引 a?int(11)?,idx_a(a) where?a?=?'123'???->錯(cuò)誤,可能導(dǎo)致未知的錯(cuò)誤,這個(gè)跟編碼有關(guān)系 where?a?=?123?????->正確
十一、利用索引做排序操作
以 idx_ab(a,b)索引為例
1、能使用上述索引進(jìn)行排序的操作是:
order?by?a; a?=?3?order?by?b; order?by?a,b; order?by?a?desc?,b?desc; a?>?5?order?by?a;
2、不能使用索引幫助排序的查詢
order?by?b;?#沒有使用到聯(lián)合索引的第一個(gè)字段 a?>?5?order?by?b;??#一旦前綴操作是一個(gè)range而非=操作,那么就無法利用到索引, 這里?a>5無法利用索引,二聯(lián)合索引的第一個(gè)字段未利用, 因此?order?by?b也無法利用索引查詢 a?in?(1,3)?order?by?b;?#in里面的值沒有建立索引,因此無法利用索引,a未用因此order?by?b也無法使用 order?by?a?asc,?b?desc;?#這里order?by?a?esc是利用了索引,但是b?desc未利用到,因?yàn)閎要和a排序方式一致才可利用到索引
十二、如何確定一個(gè)查詢有沒有走索引,走了哪些索引?
MySQL中自帶命令行工具 explain 來查看一個(gè)sql語句是否了索引
使用方式:
explain?select?*?from?tb_test;
關(guān)注的項(xiàng):
1、type?:?查詢access的方式,表的連接類型 ?????? ??????index?|??索引?? ??????full??|??全表掃描? ??????ref???|??參照查詢,也就是等值查詢?? ??????range?|??范圍查詢 2、key??:?本次查詢最終選擇使用哪個(gè)索引,NULL為未使用索引 3、key_len?:?選擇的索引使用的前綴長度或者整個(gè)長度 4、rows????:?查詢邏輯掃描過的記錄行數(shù) 5、extra???:?額外信息,主要是指fetch?data的具體方式
總結(jié):索引的本質(zhì)還是提升我們查詢數(shù)據(jù)庫的速度,減少服務(wù)器I/O開銷,提供更穩(wěn)定快捷的服務(wù)