覆蓋索引
如果一個(gè)索引包含(或者說(shuō)覆蓋)所有需要查詢的字段的值,我們就稱之為: 覆蓋索引. 覆蓋索引的好處有:
- 索引條目遠(yuǎn)小于數(shù)據(jù)行大小,能夠極大地提高性能,所以如果只需要讀取索引,那么MySQL就會(huì)極大地減少數(shù)據(jù)訪問(wèn)量
- 因?yàn)樗饕前凑罩淀樞虼鎯?chǔ)的,所以對(duì)于I/O密集型的范圍查詢會(huì)比隨機(jī)從磁盤中讀取每一行數(shù)據(jù)的I/O要少的多
- 如果不覆蓋索引,則會(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í)
索引排序和索引查詢經(jīng)常有矛盾:
如果使用某個(gè)索引進(jìn)行范圍查詢, 就無(wú)法再使用另一個(gè)索引(或該索引的后續(xù)字段)進(jìn)行排序了.范圍條件查詢和等值條件查詢有區(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)確保它們正常工作,目的如下:
- 找到并修復(fù)損壞的表
- 維護(hù)準(zhǔn)確的索引統(tǒng)計(jì)信息
- 減少碎片
找到并修復(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ǔ)引擎的索引值的分布信息, 以決定如何使用索引:
- records_in_range(),通過(guò)向存儲(chǔ)引擎?zhèn)魅雰蓚€(gè)邊界值獲取在這個(gè)范圍大概有多少條記錄
- 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ù)碎片
- 行碎片:數(shù)據(jù)行被存儲(chǔ)到多個(gè)地方的多個(gè)片段中
- 行間碎片:邏輯上順序的頁(yè),或者行在磁盤上不是順序存儲(chǔ)的
- 剩余空間碎片化:數(shù)據(jù)也中有大量的空余空間
對(duì)于MyISAM表,三類碎片都可能發(fā)生,InnoDB不會(huì)出現(xiàn)短小的行碎片.
下面三種方式都可以消除碎片化:
- OPTIMIZE TABLE
- 導(dǎo)入導(dǎo)出數(shù)據(jù)
- 不做任何操作的ALTER TABLE(標(biāo)準(zhǔn)版MySQL該方法只會(huì)消除聚簇索引的碎片化, 可以先刪除所有索引, 再alter table, 再重建索引來(lái)消除索引的碎片化)
總結(jié)
選擇索引以及利用索引查詢時(shí)的三個(gè)原則:
- 單行訪問(wèn)是很慢的. 最好讀取的塊中包含盡可能多需要的行,使用索引可以創(chuàng)建位置引用以提升效率.
- 按順序訪問(wèn)范圍數(shù)據(jù)是很快的,原因如下:
- 順序I/O不需要多次磁盤尋道,比隨機(jī)I/O快
- 如果服務(wù)器能夠按順序讀取數(shù)據(jù),那么就不再需要額外的排序操作,并且GROUP BY查詢也無(wú)須再做排序和將行按組進(jìn)行聚合計(jì)算了
- 索引覆蓋查詢是很快的, 若一個(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).