第五章 創(chuàng)建高性能的索引(下)

接上文: 第五章 創(chuàng)建高性能的索引(上)

覆蓋索引

如果一個(gè)索引包含(或者說(shuō)覆蓋)所有需要查詢的字段的值,我們就稱之為: 覆蓋索引. 覆蓋索引的好處有:

  1. 索引條目遠(yuǎn)小于數(shù)據(jù)行大小,能夠極大地提高性能,所以如果只需要讀取索引,那么MySQL就會(huì)極大地減少數(shù)據(jù)訪問(wèn)量
  2. 因?yàn)樗饕前凑罩淀樞虼鎯?chǔ)的,所以對(duì)于I/O密集型的范圍查詢會(huì)比隨機(jī)從磁盤中讀取每一行數(shù)據(jù)的I/O要少的多
  3. 如果不覆蓋索引,則會(huì)產(chǎn)生回表查詢, 先定位主鍵值,再定位行記錄,它的性能較低

當(dāng)發(fā)起一個(gè)被索引覆蓋的查詢時(shí), 在EXPLAIN的Extra列可以看到'Using index'的信息.

小技巧: 延遲關(guān)聯(lián)

select * from products where actor='SEAN CARREY' and title like '%APOLLO%';

上面的select * 包含了所有的列, 因此沒(méi)辦法使用覆蓋索引, 回表需要掃描很多不滿足條件的行. 但它的where條件是可以有索引可以覆蓋的, 利用延遲關(guān)聯(lián)(deferred join)的技巧, 建立(actor, title, prod_id)索引, 利用子查詢的覆蓋索引只過(guò)濾出滿足條件的行:

select * from products join (select prod_id from products where actor='SEAN CARREY' and title like '%APOLLO%')as t1 on (t1.prod_id=products.prod_id);

分頁(yè)查詢時(shí)這個(gè)技巧常常被使用:

-- 索引:(threa_id, deleted)
select * from t join (select id
from t where thread_id = 5616385 and deleted = 0
order by id limit 50000, 10) t1 on t.id=t1.id;

使用索引掃描來(lái)排序

MySQL有兩種方式可以生成有序的結(jié)果:通過(guò)排序操作;或者按索引順序掃描
如果EXPLAIN出來(lái)的type列的值為index,則說(shuō)明使用了索引掃描排序

只有當(dāng)索引的列順序和order by子句的順序完全一致, 且所有列的排序方向(倒序或正序)都一樣時(shí), MySQL才能使用索引來(lái)對(duì)結(jié)果排序. 當(dāng)關(guān)聯(lián)多張表, 則只有當(dāng)Order by子句引用的字段全部為第一個(gè)表時(shí), 才能使用索引來(lái)排序. 且Order By子句要滿足索引的最左前綴的要求.

下面這個(gè)例子, where子句的前綴列是范圍時(shí), 也無(wú)法使用索引掃描排序:

-- 有索引(rental_date, inventory_id, customer_id)
... where rental_date='2005-05-25' and inventory_id in (1,2) order by customer_id.

冗余和重復(fù)索引

重復(fù)索引:
MySQL允許在相同列上創(chuàng)建多個(gè)索引,但這樣需要單獨(dú)維護(hù)重復(fù)的索引,并且優(yōu)化查詢的時(shí)候也需要逐個(gè)進(jìn)行考慮,會(huì)影響性能,應(yīng)該避免這么做.

冗余索引
如果已經(jīng)創(chuàng)建了索引(A, B),在創(chuàng)建索引(A),那么就是冗余索引,因?yàn)樗皇乔耙粋€(gè)索引的前綴, 如果再創(chuàng)建(B, A), 則不是冗余索引.

冗余索引通常發(fā)生在表添加新索引的時(shí)候。如增加一個(gè)新的索引(A, B),而沒(méi)有擴(kuò)展已有索引(A),導(dǎo)致(A)成為冗余索引。或者將索引擴(kuò)展為(A, 主鍵ID),對(duì)InnoDB來(lái)說(shuō),主鍵已經(jīng)包含在二級(jí)索引中了,因此也是冗余的.

解決方法是: drop掉重復(fù)和冗余索引即可.

索引和鎖

索引可以讓查詢鎖定更少的行. nnoDB只有在訪問(wèn)行的時(shí)候才會(huì)對(duì)其加鎖, 而索引能夠減少InnoDB訪問(wèn)的行數(shù), 從而減少鎖的數(shù)量.
但這只有當(dāng)InnoDB在存儲(chǔ)引擎能夠過(guò)濾掉不需要的行時(shí)才有效,如果索引無(wú)法過(guò)濾掉無(wú)效的行,那么在InnoDB檢索到數(shù)據(jù)并返回給服務(wù)器層之后,MySQL服務(wù)器才能應(yīng)用Where子句,這時(shí)已經(jīng)無(wú)法避免鎖定行了:InnoDB已經(jīng)鎖住了這些行,到適當(dāng)?shù)臅r(shí)候才釋放。

Explain時(shí)Extra列的'using where'的意思是: MySQL服務(wù)器將存儲(chǔ)引擎返回行以后再應(yīng)用where過(guò)濾條件.

InnoDB的行鎖是建立在索引的基礎(chǔ)之上的,行鎖鎖的是索引,不是數(shù)據(jù),所以提高并發(fā)寫的能力要在查詢字段添加索引.

索引案例學(xué)習(xí)

  1. 索引排序和索引查詢經(jīng)常有矛盾:
    如果使用某個(gè)索引進(jìn)行范圍查詢, 就無(wú)法再使用另一個(gè)索引(或該索引的后續(xù)字段)進(jìn)行排序了.

  2. 范圍條件查詢和等值條件查詢有區(qū)別:
    對(duì)于范圍條件查詢, MySQL無(wú)法再使用范圍列后面的其他索引了, 而對(duì)于"多個(gè)等值條件查詢"則沒(méi)有這個(gè)限制.

select actor_id from actor where actor_id>45; -- 范圍查詢
select actor_id from actor where actor_id in (1, 4, 99); -- 等值查詢

優(yōu)化排序

-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 10;

上面的排序用到了索引, 速度是很快的. 但是當(dāng)翻頁(yè)時(shí), 靠后的查詢?nèi)匀粫?huì)很慢:

-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 100000, 10;

原因是: MySQL需要每個(gè)滿足條件的都回表取到行數(shù)據(jù), 然后丟棄. 這樣會(huì)丟棄前面大量不需要的行.

這時(shí)可以使用延遲關(guān)聯(lián)的技巧, 通過(guò)覆蓋索引查詢返回需要的主鍵, 再根據(jù)這些主鍵關(guān)聯(lián)原表獲得所需要的行:

-- 索引(sex, rating)
select <cols> from profiles inner join 
(select id from profiles where sex='M' order by rating limit 100000, 10) 
as x using(id);

維護(hù)索引和表

即使用正確的類型創(chuàng)建了表并加上了合適的索引后,還需要維護(hù)表和索引來(lái)確保它們正常工作,目的如下:

  1. 找到并修復(fù)損壞的表
  2. 維護(hù)準(zhǔn)確的索引統(tǒng)計(jì)信息
  3. 減少碎片

找到并修復(fù)損壞的表

可以通過(guò)CHECK TABLE檢查是否發(fā)生了表錯(cuò)誤
可以用REPAIR TABLE或者一個(gè)不作任何操作的ALTER操作來(lái)修復(fù)表

更新索引統(tǒng)計(jì)信息

MySQL的查詢優(yōu)化器會(huì)通過(guò)2個(gè)API來(lái)了解存儲(chǔ)引擎的索引值的分布信息, 以決定如何使用索引:

  1. records_in_range(),通過(guò)向存儲(chǔ)引擎?zhèn)魅雰蓚€(gè)邊界值獲取在這個(gè)范圍大概有多少條記錄
  2. info(),返回各種類型的數(shù)據(jù),包括索引的基數(shù)(每個(gè)鍵值有多少條記錄)

InnoDB會(huì)在表首次打開(kāi), 或者執(zhí)行analyze table, 抑或表的大小發(fā)生非常大的變化時(shí),計(jì)算索引的統(tǒng)計(jì)信息.

查看索引或表統(tǒng)計(jì)信息sql語(yǔ)句:

Select * from information_schema.statistics where table_name='actor' and table_schema='sakila’;

show index from sakila.actor;

show table status from sakila where name='actor';

注意: 查看索引統(tǒng)計(jì)信息可能會(huì)導(dǎo)致統(tǒng)計(jì)信息的更新, 造成性能問(wèn)題.

減少索引和數(shù)據(jù)的碎片

B-Tree索引可能導(dǎo)致碎片化,會(huì)導(dǎo)致查詢效率降低。有三類數(shù)據(jù)碎片

  1. 行碎片:數(shù)據(jù)行被存儲(chǔ)到多個(gè)地方的多個(gè)片段中
  2. 行間碎片:邏輯上順序的頁(yè),或者行在磁盤上不是順序存儲(chǔ)的
  3. 剩余空間碎片化:數(shù)據(jù)也中有大量的空余空間

對(duì)于MyISAM表,三類碎片都可能發(fā)生,InnoDB不會(huì)出現(xiàn)短小的行碎片.

下面三種方式都可以消除碎片化:

  1. OPTIMIZE TABLE
  2. 導(dǎo)入導(dǎo)出數(shù)據(jù)
  3. 不做任何操作的ALTER TABLE(標(biāo)準(zhǔn)版MySQL該方法只會(huì)消除聚簇索引的碎片化, 可以先刪除所有索引, 再alter table, 再重建索引來(lái)消除索引的碎片化)

總結(jié)

選擇索引以及利用索引查詢時(shí)的三個(gè)原則:

  1. 單行訪問(wèn)是很慢的. 最好讀取的塊中包含盡可能多需要的行,使用索引可以創(chuàng)建位置引用以提升效率.
  2. 按順序訪問(wèn)范圍數(shù)據(jù)是很快的,原因如下:
  • 順序I/O不需要多次磁盤尋道,比隨機(jī)I/O快
  • 如果服務(wù)器能夠按順序讀取數(shù)據(jù),那么就不再需要額外的排序操作,并且GROUP BY查詢也無(wú)須再做排序和將行按組進(jìn)行聚合計(jì)算了
  1. 索引覆蓋查詢是很快的, 若一個(gè)索引包含了查詢需要的所有列, 那就不需要再回表查詢, 這就避免了大量的單行訪問(wèn), 而第1點(diǎn)已經(jīng)寫明單行訪問(wèn)是很慢的.

現(xiàn)實(shí)使用中,很難做到每一個(gè)查詢都有完美的索引,這時(shí)候需要根據(jù)需求有所取舍地創(chuàng)建合適的索引,而非根據(jù)慣例一刀切.

如何判斷系統(tǒng)中的索引是否合理? 按響應(yīng)時(shí)間對(duì)查詢做分析, 找出消耗最長(zhǎng)時(shí)間的查詢或給服務(wù)器帶來(lái)最大壓力的查詢, 然后檢查這些查詢的schema, SQL和索引結(jié)構(gòu).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370