持久化最佳化器統計資料功能透過將統計資料儲存到磁碟並使其在伺服器重新啟動後保持持久性,來提高計劃穩定性,以便最佳化器在每次給定查詢時更可能做出一致的選擇。
當 innodb_stats_persistent=ON 時,或者當個別表格定義為 STATS_PERSISTENT=1 時,最佳化器統計資料會持續儲存到磁碟。innodb_stats_persistent 預設為啟用。
先前,最佳化器統計資料會在重新啟動伺服器以及某些其他類型的操作後清除,並在下次表格存取時重新計算。因此,在重新計算統計資料時可能會產生不同的估計值,導致查詢執行計畫的選擇不同,以及查詢效能的變化。
持久統計資料儲存在 mysql.innodb_table_stats 和 mysql.innodb_index_stats 表格中。請參閱 第 17.8.10.1.5 節「InnoDB 持久統計資料表格」。
如果您不想將最佳化器統計資料持續儲存到磁碟,請參閱 第 17.8.10.2 節「配置非持久化最佳化器統計參數」
預設為啟用的 innodb_stats_auto_recalc 變數,控制著當表格的變更超過 10% 的列時,是否自動計算統計資料。您也可以在建立或變更表格時,指定 STATS_AUTO_RECALC 子句,為個別表格設定自動統計資料重新計算。
由於自動統計資料重新計算的非同步特性,會在背景執行,即使啟用 innodb_stats_auto_recalc,在執行影響表格超過 10% 的 DML 操作後,統計資料也可能不會立即重新計算。在某些情況下,統計資料重新計算可能會延遲幾秒鐘。如果需要立即取得最新的統計資料,請執行 ANALYZE TABLE,以啟動同步 (前景) 的統計資料重新計算。
如果停用 innodb_stats_auto_recalc,您可以在對索引欄位進行重大變更後,執行 ANALYZE TABLE 陳述式,以確保最佳化工具統計資料的準確性。您也可以考慮在載入資料後執行的設定指令碼中加入 ANALYZE TABLE,並在活動量低的時段排程執行 ANALYZE TABLE。
當將索引加入現有表格,或加入或刪除欄位時,無論 innodb_stats_auto_recalc 的值為何,都會計算索引統計資料並加入 innodb_index_stats 表格中。
對於啟用 AUTO UPDATE 的直方圖(請參閱 直方圖統計分析),自動重新計算持久性統計資料也會導致直方圖更新。
innodb_stats_persistent、innodb_stats_auto_recalc 和 innodb_stats_persistent_sample_pages 是全域變數。若要覆寫這些系統範圍的設定,並為個別表格設定最佳化工具統計資料參數,您可以在 CREATE TABLE 或 ALTER TABLE 陳述式中定義 STATS_PERSISTENT、STATS_AUTO_RECALC 和 STATS_SAMPLE_PAGES 子句。
STATS_PERSISTENT指定是否為InnoDB表格啟用持久性統計資料。值DEFAULT會導致表格的持久性統計資料設定由innodb_stats_persistent設定決定。值為1會為表格啟用持久性統計資料,而值為0則會停用此功能。為個別表格啟用持久性統計資料後,請在使用ANALYZE TABLE載入表格資料後,計算統計資料。STATS_AUTO_RECALC指定是否自動重新計算持久性統計資料。值DEFAULT會導致表格的持久性統計資料設定由innodb_stats_auto_recalc設定決定。值為1會在 10% 的表格資料變更時重新計算統計資料。值0會防止表格自動重新計算。使用值 0 時,請在使用ANALYZE TABLE對表格進行重大變更後,重新計算統計資料。STATS_SAMPLE_PAGES指定在ANALYZE TABLE操作等情況下,當計算索引欄位的基數和其他統計資料時要取樣的索引頁面數。
以下 CREATE TABLE 範例指定了所有三個子句
CREATE TABLE `t1` (
`id` int(8) NOT NULL auto_increment,
`data` varchar(255),
`date` datetime,
PRIMARY KEY (`id`),
INDEX `DATE_IX` (`date`)
) ENGINE=InnoDB,
STATS_PERSISTENT=1,
STATS_AUTO_RECALC=1,
STATS_SAMPLE_PAGES=25;
最佳化工具使用關於索引鍵分佈的估計統計資料,根據索引的相對選擇性,選擇執行計畫的索引。ANALYZE TABLE 等操作會導致 InnoDB 從表格上的每個索引取樣隨機頁面,以估計索引的基數。此取樣技術稱為隨機潛水。
innodb_stats_persistent_sample_pages 控制取樣頁面的數量。您可以在執行階段調整設定,以管理最佳化工具所使用的統計資料估計品質。預設值為 20。當遇到下列問題時,請考慮修改設定
統計資料不夠準確,最佳化工具選擇了次佳的計畫,如
EXPLAIN輸出所示。您可以透過將索引的實際基數(透過在索引欄位上執行SELECT DISTINCT決定)與mysql.innodb_index_stats表格中的估計值進行比較,來檢查統計資料的準確性。如果確定統計資料不夠準確,則應增加
innodb_stats_persistent_sample_pages的值,直到統計資料估計值足夠準確為止。但是,過度增加innodb_stats_persistent_sample_pages可能會導致ANALYZE TABLE執行緩慢。ANALYZE TABLE執行速度過慢。在這種情況下,應減少innodb_stats_persistent_sample_pages,直到ANALYZE TABLE執行時間可以接受為止。但是,過度減少值可能會導致第一個問題,即統計資料不準確和次佳的查詢執行計畫。如果在準確的統計資料和
ANALYZE TABLE執行時間之間無法取得平衡,請考慮減少表格中索引欄位的數量,或限制分割區的數量,以降低ANALYZE TABLE的複雜性。表格主索引鍵中的欄位數也很重要,因為主索引鍵欄位會附加到每個非唯一索引中。
根據預設,InnoDB 在計算統計資料時會讀取未提交的資料。如果未提交的交易從表格中刪除列,則在計算列估計值和索引統計資料時,會排除已標記刪除的記錄,這可能會導致使用交易隔離等級而非 READ UNCOMMITTED 的其他同時對表格進行操作的交易,產生非最佳的執行計畫。為了避免這種情況,可以啟用 innodb_stats_include_delete_marked,以確保在計算持久性最佳化工具統計資料時包含已標記刪除的記錄。
當啟用 innodb_stats_include_delete_marked 時,ANALYZE TABLE 在重新計算統計資料時會考慮已標記刪除的記錄。
innodb_stats_include_delete_marked 是影響所有 InnoDB 表格的全域設定,而且僅適用於持久性最佳化工具統計資料。
持久性統計資料功能依賴於 mysql 資料庫中內部管理的表格,名為 innodb_table_stats 和 innodb_index_stats。這些表格會在所有安裝、升級和從原始碼建置的程序中自動設定。
表格 17.6 innodb_table_stats 的欄位
| 欄位名稱 | 描述 |
|---|---|
database_name |
資料庫名稱 |
table_name |
表格名稱、分割區名稱或子分割區名稱 |
last_update |
時間戳記,表示 InnoDB 最後一次更新此列的時間 |
n_rows |
表格中的列數 |
clustered_index_size |
主要索引的大小,以頁面為單位 |
sum_of_other_index_sizes |
其他(非主要)索引的總大小,以頁面為單位 |
表格 17.7 innodb_index_stats 的欄位
| 欄位名稱 | 描述 |
|---|---|
database_name |
資料庫名稱 |
table_name |
表格名稱、分割區名稱或子分割區名稱 |
index_name |
索引名稱 |
last_update |
表示列最後一次更新的時間的時間戳記 |
stat_name |
統計資料的名稱,其值會在 stat_value 欄位中報告 |
stat_value |
stat_name 欄位中命名的統計資料值 |
sample_size |
為 stat_value 欄位中提供的估計值取樣的頁面數 |
stat_description |
描述 stat_name 欄位中命名的統計資訊。 |
innodb_table_stats 和 innodb_index_stats 表格包含一個 last_update 欄位,顯示索引統計資訊上次更新的時間。
mysql> SELECT * FROM innodb_table_stats \G
*************************** 1. row ***************************
database_name: sakila
table_name: actor
last_update: 2014-05-28 16:16:44
n_rows: 200
clustered_index_size: 1
sum_of_other_index_sizes: 1
...mysql> SELECT * FROM innodb_index_stats \G
*************************** 1. row ***************************
database_name: sakila
table_name: actor
index_name: PRIMARY
last_update: 2014-05-28 16:16:44
stat_name: n_diff_pfx01
stat_value: 200
sample_size: 1
...innodb_table_stats 和 innodb_index_stats 表格可以手動更新,這使得在不修改資料庫的情況下,可以強制使用特定的查詢最佳化計畫或測試替代計畫。如果手動更新統計資訊,請使用 FLUSH TABLE 陳述式來載入更新後的統計資訊。tbl_name
持久性統計資訊被視為本地資訊,因為它們與伺服器實例相關。因此,當自動重新計算統計資訊時,不會複製 innodb_table_stats 和 innodb_index_stats 表格。如果您執行 ANALYZE TABLE 來啟動統計資訊的同步重新計算,該陳述式會被複製 (除非您禁止了它的記錄),並且重新計算會在複本上進行。
innodb_table_stats 表格包含每個表格的一列。以下範例示範了收集的資料類型。
表格 t1 包含一個主要索引 (欄位 a、b)、次要索引 (欄位 c、d) 和唯一索引 (欄位 e、f)。
CREATE TABLE t1 (
a INT, b INT, c INT, d INT, e INT, f INT,
PRIMARY KEY (a, b), KEY i1 (c, d), UNIQUE KEY i2uniq (e, f)
) ENGINE=INNODB;在插入五列範例資料後,表格 t1 如下所示
mysql> SELECT * FROM t1;
+---+---+------+------+------+------+
| a | b | c | d | e | f |
+---+---+------+------+------+------+
| 1 | 1 | 10 | 11 | 100 | 101 |
| 1 | 2 | 10 | 11 | 200 | 102 |
| 1 | 3 | 10 | 11 | 100 | 103 |
| 1 | 4 | 10 | 12 | 200 | 104 |
| 1 | 5 | 10 | 12 | 100 | 105 |
+---+---+------+------+------+------+若要立即更新統計資訊,請執行 ANALYZE TABLE (如果啟用了 innodb_stats_auto_recalc,假設已達到表格列變更的 10% 臨界值,統計資訊會在幾秒內自動更新)。
mysql> ANALYZE TABLE t1;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.t1 | analyze | status | OK |
+---------+---------+----------+----------+表格 t1 的表格統計資訊顯示 InnoDB 上次更新表格統計資訊的時間 (2014-03-14 14:36:34)、表格中的列數 (5)、叢集索引大小 (1 頁) 以及其他索引的合併大小 (2 頁)。
mysql> SELECT * FROM mysql.innodb_table_stats WHERE table_name like 't1'\G
*************************** 1. row ***************************
database_name: test
table_name: t1
last_update: 2014-03-14 14:36:34
n_rows: 5
clustered_index_size: 1
sum_of_other_index_sizes: 2innodb_index_stats 表格包含每個索引的多列。 innodb_index_stats 表格中的每一列都提供與特定索引統計資訊相關的資料,該統計資訊在 stat_name 欄位中命名,並在 stat_description 欄位中描述。例如:
mysql> SELECT index_name, stat_name, stat_value, stat_description
FROM mysql.innodb_index_stats WHERE table_name like 't1';
+------------+--------------+------------+-----------------------------------+
| index_name | stat_name | stat_value | stat_description |
+------------+--------------+------------+-----------------------------------+
| PRIMARY | n_diff_pfx01 | 1 | a |
| PRIMARY | n_diff_pfx02 | 5 | a,b |
| PRIMARY | n_leaf_pages | 1 | Number of leaf pages in the index |
| PRIMARY | size | 1 | Number of pages in the index |
| i1 | n_diff_pfx01 | 1 | c |
| i1 | n_diff_pfx02 | 2 | c,d |
| i1 | n_diff_pfx03 | 2 | c,d,a |
| i1 | n_diff_pfx04 | 5 | c,d,a,b |
| i1 | n_leaf_pages | 1 | Number of leaf pages in the index |
| i1 | size | 1 | Number of pages in the index |
| i2uniq | n_diff_pfx01 | 2 | e |
| i2uniq | n_diff_pfx02 | 5 | e,f |
| i2uniq | n_leaf_pages | 1 | Number of leaf pages in the index |
| i2uniq | size | 1 | Number of pages in the index |
+------------+--------------+------------+-----------------------------------+stat_name 欄位顯示以下類型的統計資訊:
size:當stat_name=size時,stat_value欄位會顯示索引中的總頁數。n_leaf_pages:當stat_name=n_leaf_pages時,stat_value欄位會顯示索引中的葉節點頁數。n_diff_pfx:當NNstat_name=n_diff_pfx01時,stat_value欄位會顯示索引第一欄中不同的值數量。當stat_name=n_diff_pfx02時,stat_value欄位會顯示索引前兩欄中不同的值數量,依此類推。當stat_name=n_diff_pfx時,NNstat_description欄位會顯示一個以逗號分隔的索引欄列表,這些欄被計數。
為了進一步說明 n_diff_pfx 統計資訊 (提供基數資料),請再次考慮先前介紹的 NNt1 表格範例。如下所示,t1 表格使用主要索引 (欄位 a、b)、次要索引 (欄位 c、d) 和唯一索引 (欄位 e、f) 建立。
CREATE TABLE t1 (
a INT, b INT, c INT, d INT, e INT, f INT,
PRIMARY KEY (a, b), KEY i1 (c, d), UNIQUE KEY i2uniq (e, f)
) ENGINE=INNODB;在插入五列範例資料後,表格 t1 如下所示
mysql> SELECT * FROM t1;
+---+---+------+------+------+------+
| a | b | c | d | e | f |
+---+---+------+------+------+------+
| 1 | 1 | 10 | 11 | 100 | 101 |
| 1 | 2 | 10 | 11 | 200 | 102 |
| 1 | 3 | 10 | 11 | 100 | 103 |
| 1 | 4 | 10 | 12 | 200 | 104 |
| 1 | 5 | 10 | 12 | 100 | 105 |
+---+---+------+------+------+------+當您查詢 index_name、stat_name、stat_value 和 stat_description,其中 stat_name LIKE 'n_diff%' 時,會傳回以下結果集:
mysql> SELECT index_name, stat_name, stat_value, stat_description
FROM mysql.innodb_index_stats
WHERE table_name like 't1' AND stat_name LIKE 'n_diff%';
+------------+--------------+------------+------------------+
| index_name | stat_name | stat_value | stat_description |
+------------+--------------+------------+------------------+
| PRIMARY | n_diff_pfx01 | 1 | a |
| PRIMARY | n_diff_pfx02 | 5 | a,b |
| i1 | n_diff_pfx01 | 1 | c |
| i1 | n_diff_pfx02 | 2 | c,d |
| i1 | n_diff_pfx03 | 2 | c,d,a |
| i1 | n_diff_pfx04 | 5 | c,d,a,b |
| i2uniq | n_diff_pfx01 | 2 | e |
| i2uniq | n_diff_pfx02 | 5 | e,f |
+------------+--------------+------------+------------------+對於 PRIMARY 索引,有兩個 n_diff% 列。列數等於索引中的欄數。
對於非唯一索引,InnoDB 會附加主鍵的欄位。
當
index_name=PRIMARY且stat_name=n_diff_pfx01時,stat_value為1,這表示索引的第一欄 (欄位a) 中有一個不同的值。藉由查看表格t1中欄位a的資料來確認欄位a中不同的值數量,其中只有一個不同的值 (1)。計數的欄位 (a) 會顯示在結果集的stat_description欄位中。當
index_name=PRIMARY且stat_name=n_diff_pfx02時,stat_value為5,這表示索引的兩個欄位 (a,b) 中有五個不同的值。藉由查看表格t1中欄位a和b的資料來確認欄位a和b中不同的值數量,其中有五個不同的值:(1,1)、(1,2)、(1,3)、(1,4) 和 (1,5)。計數的欄位 (a,b) 會顯示在結果集的stat_description欄位中。
對於次要索引 (i1),有四個 n_diff% 列。次要索引只定義了兩個欄位 (c,d),但次要索引有四個 n_diff% 列,因為 InnoDB 會在所有非唯一索引後附加主鍵。因此,有四個 n_diff% 列而不是兩個,以說明次要索引欄位 (c,d) 和主鍵欄位 (a,b)。
當
index_name=i1且stat_name=n_diff_pfx01時,stat_value為1,這表示索引的第一欄 (欄位c) 中有一個不同的值。藉由查看表格t1中欄位c的資料來確認欄位c中不同的值數量,其中只有一個不同的值:(10)。計數的欄位 (c) 會顯示在結果集的stat_description欄位中。當
index_name=i1且stat_name=n_diff_pfx02時,stat_value為2,這表示索引的前兩個欄位 (c,d) 中有兩個不同的值。藉由查看表格t1中欄位c和d的資料來確認欄位c和d中不同的值數量,其中有兩個不同的值:(10,11) 和 (10,12)。計數的欄位 (c,d) 會顯示在結果集的stat_description欄位中。當
index_name=i1且stat_name=n_diff_pfx03時,stat_value為2,這表示索引的前三個欄位 (c,d,a) 中有兩個不同的值。藉由查看表格t1中欄位c、d和a的資料來確認欄位c、d和a中不同的值數量,其中有兩個不同的值:(10,11,1) 和 (10,12,1)。計數的欄位 (c,d,a) 會顯示在結果集的stat_description欄位中。當
index_name=i1且stat_name=n_diff_pfx04時,stat_value為5,這表示索引的四個欄位 (c,d,a,b) 中有五個不同的值。藉由查看表格t1中欄位c、d、a和b的資料來確認欄位c、d、a和b中不同的值數量,其中有五個不同的值:(10,11,1,1)、(10,11,1,2)、(10,11,1,3)、(10,12,1,4) 和 (10,12,1,5)。計數的欄位 (c,d,a,b) 會顯示在結果集的stat_description欄位中。
對於唯一索引 (i2uniq),有兩個 n_diff% 列。
當
index_name=i2uniq且stat_name=n_diff_pfx01時,stat_value為2,這表示索引的第一欄 (欄位e) 中有兩個不同的值。藉由查看表格t1中欄位e的資料來確認欄位e中不同的值數量,其中有兩個不同的值:(100) 和 (200)。計數的欄位 (e) 會顯示在結果集的stat_description欄位中。當
index_name=i2uniq且stat_name=n_diff_pfx02時,stat_value為5,這表示索引的兩個欄位(e,f)中有五個不同的值。透過檢視表t1中欄位e和f的資料,可以確認欄位e和f中不同值的數量為五個:(100,101)、(200,102)、(100,103)、(200,104) 和 (100,105)。被計數的欄位 (e,f) 會顯示在結果集的stat_description欄位中。
您可以使用 innodb_index_stats 表格來檢索表格、分割區或子分割區的索引大小。在以下範例中,將檢索表格 t1 的索引大小。關於表格 t1 的定義和對應的索引統計資訊,請參閱第 17.8.10.1.6 節,「InnoDB 持久統計表格範例」。
mysql> SELECT SUM(stat_value) pages, index_name,
SUM(stat_value)*@@innodb_page_size size
FROM mysql.innodb_index_stats WHERE table_name='t1'
AND stat_name = 'size' GROUP BY index_name;
+-------+------------+-------+
| pages | index_name | size |
+-------+------------+-------+
| 1 | PRIMARY | 16384 |
| 1 | i1 | 16384 |
| 1 | i2uniq | 16384 |
+-------+------------+-------+對於分割區或子分割區,您可以使用相同的查詢,並修改 WHERE 子句來檢索索引大小。例如,以下查詢檢索表格 t1 的分割區索引大小。
mysql> SELECT SUM(stat_value) pages, index_name,
SUM(stat_value)*@@innodb_page_size size
FROM mysql.innodb_index_stats WHERE table_name like 't1#P%'
AND stat_name = 'size' GROUP BY index_name;