高性能索引(下)

選擇合適的索引列順序

  我們遇到的最容易起困惑的問題就是索引列的順序。正確是順序一來與使用該索引的查詢,並且同時需要考慮如何更好的滿足排序和分組的需要(順便說明,本節內容使用與B-Tree索引;hash或者其他類型的索引並不會像B-Tree索引一樣按照順序順序存儲數據)。

  在一個多列的B-Tree索引中,索引列的順序意味着索引首先按照最左列進行排序,其次是第二列,等等等。所以,索引可以按照升序或者降序搜啊嗎,以滿足精確符合列順序的ORDER BY ,GROUP BY 和DISTICT 等子句的查詢需求。

  所以多列索引的順序至關重要。在Lahdenmaki和Leach的“三星索引”系統中,列順序也決定了一個索引是否能夠成爲一個真正的“三星索引”

  對於如何選擇索引的順序有一個經驗法則:將選擇性最高的列放到索引的最前列。這個建議有用嗎?在某些場景中可能有幫助,但通常不如避免隨機IO和拍下那麼在湖南工業,考慮問題需要全面(場景不同則選擇不同,沒有一個放之四海皆準的法則。這裏只是說明這個經驗法則可能沒有那麼重要)。

  當不需要考慮拍下和分組是,將選擇性最高的列放在前面通常是最好的。這時候索引的作用只是用於優化where條件的查找。在這種情況下,這樣設計的索引確實能夠最快的過濾掉需要的行,對於在where子句中只使用了索引不發前綴列的查詢來說選擇性也更高。然而,性能不只是依賴於所有索引列的選擇性(整體基數),也和查詢條件的具體值有關,也只是和值的分佈有關。這和前面介紹的選擇前綴的長度需要考慮的地方一樣,可能需要根據那些運行頻率更高的查詢來調整索引列的順序,讓這種情況下的選擇性更高。

  以下面的查詢爲例:

  SELECT * FROM  payment WHERE staff_id = 2 AND customer_id =593;

  是應該創建一個(staff_id , customer_id)索引還是應該點到一下順序?可以跑一些查詢來確定這個表中值的分佈情況,並確定那個列的選擇性更高。先用下面的查詢預測一下,看看哥哥where條件的分支對應的數據技術有多大:

  SELECT SUM(staff_id = 2) ,SUM(CUSTOMER_ID=593) FROM payment /G;

 ************************************************************

SUM(STAFF_ID = 2) :7992

SUM(CUSTOMER_ID=593):30

根據前面的經驗法則,應該講索引列customer_id放到前面,因爲對應條件值的customer_id數量更小。我們再來看看對於這個customer_id的條件值,對應的staff_id列的選擇性如何:

  SELECT SUM(staff_id=2) FROM payment WHERE customer_id=593 /G

*************************************************************

SUM(staff_id=2) :17

  這樣做有一個地方需要注意,查詢的結果非常依賴於選定的具體值。如果按照上面的辦法優化,可能對其他一些條件查詢不公平,服務器的整體性能可能變得更糟,或者其他的查詢條件運行變得不如預期。

  如果是從諸如pt-query-digest這樣的工具報告中提取“最差”的查詢,那麼再按上述辦法選定的索引順序往往是非常高效的。如果沒有類似的具體查詢來運行,那麼最好還是按照經驗法則來做,因爲經驗法則考慮的全局基數和選擇性,而不是某個具體查詢:

  SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,count(*) FROM payment /G

*************************************************************

staff_id_selectivity:0.00001

customer_id_selectivity:0.0373

count(*):16049

所以customer_id的選擇性更高,所以答案是將其作爲索引列的第一列:

ALTER TABLE payment ADD KEY(customer_id,staff_id);

  當使用前綴索引的時候,在某些條件值的基數比正常值高的時候,問題就來了。例如:在某些應用中,對於沒有登錄的用戶,都將其用戶名記爲 “guset”,在記錄用戶行爲的會話表和其他記錄用戶活動的表中,“guest”就成爲了一個特殊用戶ID,一旦查詢涉及到這個用戶,那麼和對於正常用戶的查詢就打不相同了,以爲通常有很多會話都是沒有登錄的。系統賬號也會導致類似的問題。一個應用通常都有一個特殊的管理員賬號,和普通賬號不同,它補補水一個具體的用戶,系統中所有的其他用戶都是這個用戶的好友,所以系統往往通過它像王子的所有用戶發送狀態通知到和其他消息。這個賬號的巨大的好友列表容易導致王子出現服務器性能問題。

  這實際山是一個非常典型的問題。任何異常的用戶,不僅僅是那些用於管應用的設計糟糕的戰壕會有同樣的問題;那些擁有大量好友,圖片,狀態,收藏的用戶,也會有前面提到的系統賬號同樣的問題。

  下面是一個我們遇到過的真實案例,在一個用戶分享購買商品和購買經驗的論壇上,這個特殊表上的查詢運行的非常慢:

  SELECT COUNT(DISTINCT threadId) as COUNT_VALUE FROM message WHERE (groupId = 10137) AND (user_id = 1288826) and (anoymous = 0) ORDER BY priority DESC ,modifiedDate DESC

  這個竄稀看似沒有建立合適的索引,所以客戶諮詢我們是否可以優化。EXPLAIN的結果如下:

  id:1

  select_type:SIMPLE

  table:Message

  type:ref

  key:ix_groupId_userId

  key_len:18

  ref:const,const

  rows:1251162

  Extra:Using Where

MySQL 爲這個查詢選擇了索引(groupid,userid),如果不考慮列的基數,這看起來是一個非常合理的選擇。但如果考慮一下userId和groupId條件匹配的行數,可能就會有不同的想法了:

  SELECT COUNT(*) , SUM(group_id=10137),sum(userId=1288826),sum(anonymous=0) FROM Message /G

  *******************************************************

COUNT(*):4142217

SUM(group_id=10137):4092654

sum(userid=1288826) :1288496

sum(anoymous=0):414394

  從上面看符合組(groupid)條件幾乎滿足表中的所有行,符合用戶(userid)條件有130W條記錄------也就是說,索引基本上沒神馬用。因爲這些數據是從其他應用中遷移過來的,遷移的時候吧所有的消息屬性都賦予了管理員組的用戶。這個案例的解決辦法是修改應用程序代碼,區分這類特殊用戶和組,進制針對這類用戶和組執行這個查詢。

  從這個小案例中可以看到經驗法則和推理在多數情況中是有用的,但要注意不要假設平局情況向的性能也能代表特性情況先的系能,特殊情況可能會摧毀整個應用的性能。

  最後儘管關於選擇上線和技術的經驗法則值得去研究和分析,但一定不要忘了WHERE 子句中的排序,分組和範圍條件等其他因素,這些因素可能對查詢的性能帶來非常大的影響。


原文:http://www.cnblogs.com/zhengyanqiu/p/4979547.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章