持久化最佳化器統計資訊功能透過將統計資訊儲存到磁碟並使其在伺服器重新啟動後保持持久化,來改善計畫穩定性,使最佳化器更有可能每次對給定查詢做出一致的選擇。
當 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
的複雜性。資料表主要索引中的欄數也很重要,因為主要索引欄會附加到每個非唯一索引。如需相關資訊,請參閱 第 17.8.10.3 節「估計 InnoDB 資料表的 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
)中有一個相異值。欄a
中的相異值數量可透過檢視資料表t1
中欄a
的資料來確認,其中有一個相異值(1
)。計數的欄(a
)會顯示在結果集的stat_description
欄中。當
index_name
=PRIMARY
且stat_name
=n_diff_pfx02
時,stat_value
為5
,這表示索引的兩欄(a,b
)中有五個相異值。欄a
和b
中的相異值數量可透過檢視資料表t1
中欄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
)中有一個相異值。欄c
中的相異值數量可透過檢視資料表t1
中欄c
的資料來確認,其中有一個相異值:(10
)。計數的欄(c
)會顯示在結果集的stat_description
欄中。當
index_name
=i1
且stat_name
=n_diff_pfx02
時,stat_value
為2
,這表示索引的前兩欄(c,d
)中有兩個相異值。欄c
和d
中的相異值數量可透過檢視資料表t1
中欄c
和d
的資料來確認,其中有兩個相異值:(10,11
) 和 (10,12
)。計數的欄(c,d
)會顯示在結果集的stat_description
欄中。當
index_name
=i1
且stat_name
=n_diff_pfx03
時,stat_value
為2
,這表示索引的前三欄(c,d,a
)中有兩個相異值。欄c
、d
和a
中的相異值數量可透過檢視資料表t1
中欄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
)中有五個相異值。欄c
、d
、a
和b
中的相異值數量可透過檢視資料表t1
中欄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
)中有兩個相異值。欄e
中的相異值數量可透過檢視資料表t1
中欄e
的資料來確認,其中有兩個相異值:(100
) 和 (200
)。計數的欄(e
)會顯示在結果集的stat_description
欄中。當
index_name
=i2uniq
且stat_name
=n_diff_pfx02
時,stat_value
為5
,這表示索引的兩欄(e,f
)中有五個相異值。欄e
和f
中的相異值數量可透過檢視資料表t1
中欄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;