1) 什么是Cardinality
不是所有的查詢條件出現的列都需要添加索引。對於什么時候添加B+樹索引。一般的經驗是,在訪問表中很少一部分時使用B+樹索引才有意義。對於性別字段、地區字段、類型字段,他們可取值范圍很小,稱為低選擇性。如
SELECT * FROM student WHERE sex='M'
按性別進行查詢時,可取值一般只有M、F。因此SQL語句得到的結果可能是該表50%的數據(加入男女比例1:1)這時添加B+樹索引是完全沒有必要的。相反,如果某個字段的取值范圍很廣,幾乎沒有重復,屬於高選擇性。則此時使用B+樹的索引是最合適的。例如對於姓名字段,基本上在一個應用中不允許重名的出現
怎樣查看索引是否有高選擇性?通過SHOW INDEX結果中的列Cardinality來觀察。非常關鍵,表示所以中不重復記錄的預估值,需要注意的是Cardinality是一個預估值,而不是一個准確值基本上用戶也不可能得到一個准確的值,在實際應用中,Cardinality/n_row_in_table應盡可能的接近1,如果非常小,那用戶需要考慮是否還有必要創建這個索引。故在訪問高選擇性屬性的字段並從表中取出很少一部分數據時,對於字段添加B+樹索引是非常有必要的。如
SELECT * FROM member WHERE usernick='David';
表member大約有500W行數據,usernick字段上有一個唯一索引。這是如果查找用戶名為David的用戶,將得到如下執行計划
可以看到使用了usernick這個索引。這也符合之前提到的高可選擇性,即SQL語句取表中較少行的原則
2) InnoDB存儲引擎的Cardinality統計
建立索引的前提是高選擇性。這對數據庫來說才具有實際意義,那么數據庫是怎樣統計Cardinality的信息呢?因為MySQL數據庫中有各種不同的存儲引擎,而每種存儲引擎對於B+樹索引的實現又各不相同。所以對Cardinality統計時放在存儲引擎層進行的
在生成環境中,索引的更新操作可能非常頻繁。如果每次索引在發生操作時就對其進行Cardinality統計,那么將會對數據庫帶來很大的負擔。另外需要考慮的是,如果一張表的數據非常大,如一張表有50G的數據,那么統計一次Cardinality信息所需要的時間可能非常長。這樣的環境下,是不能接受的。因此,數據庫對於Cardinality信息的統計都是通過采樣的方法完成
在InnoDB存儲引擎中,Cardinality統計信息的更新發生在兩個操作中:insert和update。InnoDB存儲引擎內部對更新Cardinality信息的策略為:
表中1/16的數據已發生了改變
stat_modified_counter>2000 000 000
第一種策略為自從上次統計Cardinality信息后,表中的1/16的數據已經發生過變化,這是需要更新Cardinality信息
第二種情況考慮的是,如果對表中某一行數據頻繁地進行更新操作,這時表中的數據實際並沒有增加,實際發生變化的還是這一行數據,則第一種更新策略就無法適用這種情況,故在InnoDB存儲引擎內部有一個計數器start_modified_counter,用來表示發生變化的次數,當start_modified_counter>2 000 000 000 時,則同樣更新Cardinality信息
接着考慮InnoDB存儲引擎內部是怎樣進行Cardinality信息統計和更新操作呢?同樣是通過采樣的方法。默認的InnoDB存儲引擎對8個葉子節點Leaf Page進行采用。采用過程如下
取得B+樹索引中葉子節點的數量,記為A
隨機取得B+樹索引中的8個葉子節點,統計每個頁不同記錄的個數,即為P1,P2....P8
通過采樣信息給出Cardinality的預估值:Cardinality=(P1+P2+...+P8)*A/8
根據上述的說明可以發現,在InnoDB存儲引擎中,Cardinality值通過對8個葉子節點預估而得的。而不是一個實際精確的值。再者,每次對Cardinality值的統計,都是通過隨機取8個葉子節點得到的,這同時有暗示了另外一個Cardinality現象,即每次得到的Cardinality值可能不同的,如
SHOW INDEX FROM OrderDetails
上述SQL語句會觸發MySQL數據庫對於Cardinality值的統計,第一次運行得到的結果如圖5-20
在上述測試過程中,並沒有通過INSERT、UPDATE這類的操作來改變OrderDetails中的內容,但是當第二次運行SHOW INDEX FROM OrderDetails語句是,發生了變化,如圖5-21
可以看到,當第二次運行SHOW INDEX FROM OrderDetails語句時,表OrderDetails索引中的Cardinality值發生了變化,雖然表OrderDetails本身並沒有發生任何變化,但是由於Cardinality是隨機取8個葉子節點進行分析,所以即使表沒有發生變化,用戶觀察到索引Cardinality值還是會發生變化,這本身不是Bug,而是隨機采樣而導致的結果
當然,有一種情況可以使得用戶每次觀察到的索引Cardinality值是一樣的。那就是表足夠小,表的葉子節點樹小於或者等於8個。這時即使隨機采樣,也總是會采取倒這些頁,因此每次得到的Cardinality值是相同的
在InnoDB1.2版本之前,可以通過innodb_stats_sample_pages用來設置統計Cardinality時每次采樣頁的數量,默認為8.同時,參數innodb_stats_method用來判斷如何對待索引中出現NULL值記錄。該參數默認值為nulls_equal,表示將NULL值記錄為相等的記錄。其有效值還nulls_unequal,nulls_ignored,分別表示將NULL值記錄視為不同的記錄和忽略NULL值記錄。例如某夜中索引記錄為NULL、NULL、1、2、2、3、3、3,在參數innodb_stats_method默認設置下,該頁的Cardinality為4;若參數innodb_stats_method為nulls_unequal,則該頁的Cardinality為5,若參數innodb_stats_method為nulls_ignored,則Cardinality值為3
當執行ANALYZE TABLE、SHOW TABLE STATUS、SHOW INDEX 以及訪問INFORMATION_SCHEMA架構下的表TABLES和STATISTICS時會導致InnoDB存儲引擎會重新計算索引Cardinality值,若表中的數據量非常大,並且表中存在多個輔助索引時,執行上述操作可能會非常慢,雖然用戶可能並不希望去更新Cardinality值
InnoDB1.2版本提供了更多參數對Cardinality進行設置。如表