Mysql深度講解 - 查詢優化器(二)
前言
上一篇【Mysql深度講解 - 查詢優化器(一)】主要講了查詢優化器怎麼用,以及查詢優化的成本計算,那麼這些成本資料是怎麼統計呢?本篇將會繼續對Mysql中查詢優化器的資料統計做一個解析。更多Mysql調優內容請點選【Mysql優化-深度講解系列目錄】。
儲存統計資料
InnoDB提供了兩種方式儲存統計資料:
- 儲存在磁碟上。
- 儲存在記憶體中,當伺服器關閉時這些統計資料也被清掉。
當前統計資料的儲存方式可以通過檢視系統變數innodb_stats_persistent
來檢視儲存方式。innodb_stats_persistent
的值是ON
就代表著統計資料被儲存到磁碟中。反之為OFF
innodb_stats_persistent
預設為OFF
,之後的版本中預設情況下都是ON
的狀態,也就是說5.6.6以後都是預設存在磁碟上。這個資料可以通過命令:SHOW VARIABLES like 'innodb_stats_persistent'
檢視。
由於InnoDB本身就是以表為單位收集並存儲統計資料的,所以可以把某些表的統計資料(包括索引統計資料)儲存在磁碟上,把另一些表的統計資料儲存記憶體中,比如再建立或者修改表的時候指定stats_persistent=1(ON)
或者stats_persistent=0(OFF)
來指定該表統計資料的儲存位置。
當我們把某個表所有的統計資料儲存到磁碟上時,InnoDB其實是把這部分資料儲存到了下面兩張表裡:
- innodb_table_stats:儲存表的統計資料,每條記錄對應一個表的統計資料。
- innodb_index_stats:儲存索引的統計資料,每條記錄對應一個索引的一個統計項的統計資料。
統計資料的更新
Mysql中是否自動重新計算統計資料是由系統變數innodb_stats_auto_recalc
控制的,其預設是ON
也就是自動更新預設是開啟的。Mysql中每個表都維護了一個變數,用於記錄對該表進行的增刪改操作的數量。如果修改記錄數超過了該表總記錄的10%
,並且變數innodb_stats_auto_recalc=ON
innodb_table_stats表
和 innodb_index_stats表
的相關資料。但是這個過程是非同步執行的,也就是說即使表的資料改變超過了10%
,也不會立即反映到兩個表上,會有一定的延時。可以使用命令:SHOW VARIABLES like 'innodb_stats_auto_recalc'
檢視該變數的狀態。
如果innodb_stats_auto_recalc
的值為OFF
,也同樣可以呼叫Mysql中預留的命令去手動更新統計資料,更新的命令為:ANALYZE TABLE table_name
。或者在table maintenance
介面點選Analyze Table
按鈕。當覺得Mysql的成本計算器沒有按照預想的使用目標索引的時候,可以嘗試先更新統計資料,再進行計算成本。
索引統計資料
在利用索引去執行sql語句查詢時,可能會有許多的單點區間,比如使用in語句
是就會使得查詢語句包含很多單點區間,例如:
Select * from table1 where name in ('a','b','c','d', ...);
假設這個table1
表有一個針對name
欄位建立的索引idx_tb1_nm
。由於這個索引不一定是唯一二級索引,所以並不能確定一個單點區間對應的二級索引記錄的條數有多少。所以Mysql只能先獲取索引對應的B+樹的區間最左記錄和區間最右記錄,然後再計算這兩條記錄之間有多少記錄。通過直接訪問索引對應的B+樹來計算某個範圍區間對應的索引記錄條數的方式稱之為index dive
。
如果只有少量單點區間,使用index dive
的方式去統計相應的記錄數並不會佔用太多的資源。可是如果單點區間很多,比如有1000個,那麼MySQL的查詢優化器要進行1000次 index dive
操作,才能計算出這些單點區間對應的索引記錄條數。單點區間的數量越多,對應的index dive
操作次數也就越多,對於資料庫效能就是一個非常大的消耗。所以MySQL提供了一個系統變數eq_range_index_dive_limit
, 可以使用命:SHOW VARIABLES LIKE 'eq_range_index_dive_limit'
檢視,預設值為200。
也就是說Mysql預設使用index dive
操作的最大單點區間個數為200。如果大於200就會使用索引統計資料來進行估算,剛才已經說了innodb_index_stats
表存的就是索引的統計資料,此時就用的上了,可以使用SHOW INDEX FROM table_name
命令去檢視,這裡就以上篇中的city表為例:SHOW INDEX FROM city
。
Table | Key_name | Column_name | Cardinality | Index_type |
---|---|---|---|---|
city | PRIMARY | ID | 4117 | BTREE |
city | CountryCode | CountryCode | 232 | BTREE |
city | idx_city_NP | Name | 4001 | BTREE |
city | idx_city_NP | Population | 4079 | BTREE |
結合show table status like 'city'
一起分析,這個命令是顯示當前表的統計資料。
Name | Engine | Row_format | Rows | Data_length | Index_length | …… |
---|---|---|---|---|---|---|
city | InnoDB | Dynamic | 4117 | 393216 | 81920 | …… |
這裡面最重要的就是Cardinality
屬性,表示索引列中不重複值的個數。可以看到city表中一共有4117行資料,而ID這個欄位的Cardinality
屬性也是4117,這就意味著該列中沒有任何的重複資料。如果Cardinality
屬性為1,意味著該列中只有一個值,也就是全部都是重複的資料。不過要注意的是Cardinality
屬性也是一個估算值,是用來估算查詢成本用的,並不是精確值。可以根據這個屬性來估算in語句
中的引數所對應的記錄數:
- 使用SHOW TABLE STATUS語句查詢Rows值。
- 使用SHOW INDEX語句查詢Cardinality值。
- 根據上面兩個值可以算出某個索引對於某個列平均單個值的重複次數Rows/Cardinality
- 所以總回表的記錄數就是:in語句中的引數個數*Rows/Cardinality
NULL值處理
上面已經說了,統計資料中的不重複列對於計算成本是一個很重要的指標。對於如何理解NULL
值,會對查詢優化器造成很大的影響,一般來說有三種理解方式:
- NULL值代表一個未確定的值,每個NULL值都是獨一無二的,在統計列不重複值的時候應該都當作獨立的。
- NULL值在業務上就是代表空值, NULL值所代表的意義是一樣的,就是代表該cell不需要填充任何資料,在統計列不重複值的時候應該合併只計算為一個值。
- NULL完全沒有意義,在統計列不重複值的時候應該被忽略。
Mysql同樣也提供了一個系統變數innodb_stats_method
來表示處理NULL值得策略,檢視該變數值可以使用show variables like 'innodb_stats_method'
命令,一共有三種值:
- nulls_equal:Mysql自設的預設值。代表所有NULL值都是相等的。如果某個索引列中NULL值特別多的話,這種統計方式會讓優化器認為某個列中平均一個值重複次數特別多,所以傾向於不使用索引進行訪問。
- nulls_unequal:認為所有NULL值都是不相等的。如果某個索引列中NULL值特別多的話,這種統計方式 會讓優化器認為某個列中平均一個值重複次數特別少,所以傾向於使用索引進行訪問。
- nulls_ignored:直接把NULL值忽略掉。
因此索引列最好不要有NULL值。但是如果一定要索引中有NULL值存在,也是能夠利用到索引的,但是也是要符合最左原則。比如:
表:t1(a,b,c,d),index:t1(b,c,d)
Select * from t1 where b is null; -- 可以利用到索引,因為null值是最小的值,會被排列在最左,符合索引最左原則
Select * from t1 where b is not null; -- 不能利用到索引,因為後續的索引全部都不是null,不符合最左原則
總結
那麼到此查詢優化器的知識基本上結束了,希望通過筆者的帖子大家能夠掌握查詢優化器是如何使用的,以及各種表中各種屬性是如何影響到查詢優化器進行成本操作的,以及如何選用的查詢方式。那麼下一篇【Mysql深度講解 – Join語句】將會主要對Join欄位進行一個講解。