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

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

覆蓋索引

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

  1. 索引條目遠小于數據行大小,能夠極大地提高性能,所以如果只需要讀取索引,那么MySQL就會極大地減少數據訪問量
  2. 因為索引是按照值順序存儲的,所以對于I/O密集型的范圍查詢會比隨機從磁盤中讀取每一行數據的I/O要少的多
  3. 如果不覆蓋索引,則會產生回表查詢, 先定位主鍵值,再定位行記錄,它的性能較低

當發起一個被索引覆蓋的查詢時, 在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的行鎖是建立在索引的基礎之上的,行鎖鎖的是索引,不是數據,所以提高并發寫的能力要在查詢字段添加索引.

索引案例學習

  1. 索引排序和索引查詢經常有矛盾:
    如果使用某個索引進行范圍查詢, 就無法再使用另一個索引(或該索引的后續字段)進行排序了.

  2. 范圍條件查詢和等值條件查詢有區別:
    對于范圍條件查詢, 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);

維護索引和表

即使用正確的類型創建了表并加上了合適的索引后,還需要維護表和索引來確保它們正常工作,目的如下:

  1. 找到并修復損壞的表
  2. 維護準確的索引統計信息
  3. 減少碎片

找到并修復損壞的表

可以通過CHECK TABLE檢查是否發生了表錯誤
可以用REPAIR TABLE或者一個不作任何操作的ALTER操作來修復表

更新索引統計信息

MySQL的查詢優化器會通過2個API來了解存儲引擎的索引值的分布信息, 以決定如何使用索引:

  1. records_in_range(),通過向存儲引擎傳入兩個邊界值獲取在這個范圍大概有多少條記錄
  2. 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索引可能導致碎片化,會導致查詢效率降低。有三類數據碎片

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

對于MyISAM表,三類碎片都可能發生,InnoDB不會出現短小的行碎片.

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

  1. OPTIMIZE TABLE
  2. 導入導出數據
  3. 不做任何操作的ALTER TABLE(標準版MySQL該方法只會消除聚簇索引的碎片化, 可以先刪除所有索引, 再alter table, 再重建索引來消除索引的碎片化)

總結

選擇索引以及利用索引查詢時的三個原則:

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

現實使用中,很難做到每一個查詢都有完美的索引,這時候需要根據需求有所取舍地創建合適的索引,而非根據慣例一刀切.

如何判斷系統中的索引是否合理? 按響應時間對查詢做分析, 找出消耗最長時間的查詢或給服務器帶來最大壓力的查詢, 然后檢查這些查詢的schema, SQL和索引結構.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374