接上文: 第五章 創建高性能的索引(上)
覆蓋索引
如果一個索引包含(或者說覆蓋)所有需要查詢的字段的值,我們就稱之為: 覆蓋索引. 覆蓋索引的好處有:
- 索引條目遠小于數據行大小,能夠極大地提高性能,所以如果只需要讀取索引,那么MySQL就會極大地減少數據訪問量
- 因為索引是按照值順序存儲的,所以對于I/O密集型的范圍查詢會比隨機從磁盤中讀取每一行數據的I/O要少的多
- 如果不覆蓋索引,則會產生回表查詢, 先定位主鍵值,再定位行記錄,它的性能較低
當發起一個被索引覆蓋的查詢時, 在EXPLAIN的Extra列可以看到'Using index'的信息.
小技巧: 延遲關聯
select * from products where actor='SEAN CARREY' and title like '%APOLLO%';
上面的select * 包含了所有的列, 因此沒辦法使用覆蓋索引, 回表需要掃描很多不滿足條件的行. 但它的where條件是可以有索引可以覆蓋的, 利用延遲關聯(deferred join)的技巧, 建立(actor, title, prod_id)索引, 利用子查詢的覆蓋索引只過濾出滿足條件的行:
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);
分頁查詢時這個技巧常常被使用:
-- 索引:(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;
使用索引掃描來排序
MySQL有兩種方式可以生成有序的結果:通過排序操作;或者按索引順序掃描
如果EXPLAIN出來的type列的值為index,則說明使用了索引掃描排序
只有當索引的列順序和order by子句的順序完全一致, 且所有列的排序方向(倒序或正序)都一樣時, MySQL才能使用索引來對結果排序. 當關聯多張表, 則只有當Order by子句引用的字段全部為第一個表時, 才能使用索引來排序. 且Order By子句要滿足索引的最左前綴的要求.
下面這個例子, where子句的前綴列是范圍時, 也無法使用索引掃描排序:
-- 有索引(rental_date, inventory_id, customer_id)
... where rental_date='2005-05-25' and inventory_id in (1,2) order by customer_id.
冗余和重復索引
重復索引:
MySQL允許在相同列上創建多個索引,但這樣需要單獨維護重復的索引,并且優化查詢的時候也需要逐個進行考慮,會影響性能,應該避免這么做.
冗余索引
如果已經創建了索引(A, B),在創建索引(A),那么就是冗余索引,因為它只是前一個索引的前綴, 如果再創建(B, A), 則不是冗余索引.
冗余索引通常發生在表添加新索引的時候。如增加一個新的索引(A, B),而沒有擴展已有索引(A),導致(A)成為冗余索引。或者將索引擴展為(A, 主鍵ID),對InnoDB來說,主鍵已經包含在二級索引中了,因此也是冗余的.
解決方法是: drop掉重復和冗余索引即可.
索引和鎖
索引可以讓查詢鎖定更少的行. nnoDB只有在訪問行的時候才會對其加鎖, 而索引能夠減少InnoDB訪問的行數, 從而減少鎖的數量.
但這只有當InnoDB在存儲引擎能夠過濾掉不需要的行時才有效,如果索引無法過濾掉無效的行,那么在InnoDB檢索到數據并返回給服務器層之后,MySQL服務器才能應用Where子句,這時已經無法避免鎖定行了:InnoDB已經鎖住了這些行,到適當的時候才釋放。
Explain時Extra列的'using where'的意思是: MySQL服務器將存儲引擎返回行以后再應用where過濾條件.
InnoDB的行鎖是建立在索引的基礎之上的,行鎖鎖的是索引,不是數據,所以提高并發寫的能力要在查詢字段添加索引.
索引案例學習
索引排序和索引查詢經常有矛盾:
如果使用某個索引進行范圍查詢, 就無法再使用另一個索引(或該索引的后續字段)進行排序了.范圍條件查詢和等值條件查詢有區別:
對于范圍條件查詢, MySQL無法再使用范圍列后面的其他索引了, 而對于"多個等值條件查詢"則沒有這個限制.
select actor_id from actor where actor_id>45; -- 范圍查詢
select actor_id from actor where actor_id in (1, 4, 99); -- 等值查詢
優化排序
-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 10;
上面的排序用到了索引, 速度是很快的. 但是當翻頁時, 靠后的查詢仍然會很慢:
-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 100000, 10;
原因是: MySQL需要每個滿足條件的都回表取到行數據, 然后丟棄. 這樣會丟棄前面大量不需要的行.
這時可以使用延遲關聯的技巧, 通過覆蓋索引查詢返回需要的主鍵, 再根據這些主鍵關聯原表獲得所需要的行:
-- 索引(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);
維護索引和表
即使用正確的類型創建了表并加上了合適的索引后,還需要維護表和索引來確保它們正常工作,目的如下:
- 找到并修復損壞的表
- 維護準確的索引統計信息
- 減少碎片
找到并修復損壞的表
可以通過CHECK TABLE檢查是否發生了表錯誤
可以用REPAIR TABLE或者一個不作任何操作的ALTER操作來修復表
更新索引統計信息
MySQL的查詢優化器會通過2個API來了解存儲引擎的索引值的分布信息, 以決定如何使用索引:
- records_in_range(),通過向存儲引擎傳入兩個邊界值獲取在這個范圍大概有多少條記錄
- info(),返回各種類型的數據,包括索引的基數(每個鍵值有多少條記錄)
InnoDB會在表首次打開, 或者執行analyze table, 抑或表的大小發生非常大的變化時,計算索引的統計信息.
查看索引或表統計信息sql語句:
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';
注意: 查看索引統計信息可能會導致統計信息的更新, 造成性能問題.
減少索引和數據的碎片
B-Tree索引可能導致碎片化,會導致查詢效率降低。有三類數據碎片
- 行碎片:數據行被存儲到多個地方的多個片段中
- 行間碎片:邏輯上順序的頁,或者行在磁盤上不是順序存儲的
- 剩余空間碎片化:數據也中有大量的空余空間
對于MyISAM表,三類碎片都可能發生,InnoDB不會出現短小的行碎片.
下面三種方式都可以消除碎片化:
- OPTIMIZE TABLE
- 導入導出數據
- 不做任何操作的ALTER TABLE(標準版MySQL該方法只會消除聚簇索引的碎片化, 可以先刪除所有索引, 再alter table, 再重建索引來消除索引的碎片化)
總結
選擇索引以及利用索引查詢時的三個原則:
- 單行訪問是很慢的. 最好讀取的塊中包含盡可能多需要的行,使用索引可以創建位置引用以提升效率.
- 按順序訪問范圍數據是很快的,原因如下:
- 順序I/O不需要多次磁盤尋道,比隨機I/O快
- 如果服務器能夠按順序讀取數據,那么就不再需要額外的排序操作,并且GROUP BY查詢也無須再做排序和將行按組進行聚合計算了
- 索引覆蓋查詢是很快的, 若一個索引包含了查詢需要的所有列, 那就不需要再回表查詢, 這就避免了大量的單行訪問, 而第1點已經寫明單行訪問是很慢的.
現實使用中,很難做到每一個查詢都有完美的索引,這時候需要根據需求有所取舍地創建合適的索引,而非根據慣例一刀切.
如何判斷系統中的索引是否合理? 按響應時間對查詢做分析, 找出消耗最長時間的查詢或給服務器帶來最大壓力的查詢, 然后檢查這些查詢的schema, SQL和索引結構.