持久化最佳化器統計資料功能透過將統計資料儲存到磁碟並使其在伺服器重新啟動後保持持久性,來提高計劃穩定性,以便最佳化器在每次給定查詢時更可能做出一致的選擇。
當 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: 2
innodb_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
:當NN
stat_name
=n_diff_pfx01
時,stat_value
欄位會顯示索引第一欄中不同的值數量。當stat_name
=n_diff_pfx02
時,stat_value
欄位會顯示索引前兩欄中不同的值數量,依此類推。當stat_name
=n_diff_pfx
時,NN
stat_description
欄位會顯示一個以逗號分隔的索引欄列表,這些欄被計數。
為了進一步說明 n_diff_pfx
統計資訊 (提供基數資料),請再次考慮先前介紹的 NN
t1
表格範例。如下所示,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;