17.8.10.1 配置持久优化器统计参数
持久优化器统计特性通过将统计信息存储到磁盘中,并使其在服务器重启时保持不变,从而提高计划稳定性,使得优化器更有可能在给定的查询中每次都做出一致的选择。
优化器统计信息将在innodb_stats_persistent=ON
或在单个表中定义时使用STATS_PERSISTENT=1
。innodb_stats_persistent
默认情况下是启用的。
之前,优化器统计信息在服务器重启时和一些其他操作后被清除,并且在下一次表访问时重新计算。因此,在重新计算统计信息时可能会产生不同的估算,从而导致查询执行计划的不同选择和查询性能的变化。
持久统计信息存储在mysql.innodb_table_stats和mysql.innodb_index_stats表中。请参阅第17.8.10.1.5节,“InnoDB Persistent Statistics Tables”。
如果您不想将优化器统计信息持久存储到磁盘中,请参阅第17.8.10.2节,“非持久优化器统计参数配置”
17.8.10.1.1 非持久优化器统计自动计算
默认情况下启用的innodb_stats_auto_recalc
变量控制是否在表发生变化时自动计算统计信息(当变化的行数超过表的10%)。您也可以使用STATS_AUTO_RECALC
子句在创建或修改表时配置自动统计信息重新计算。
由于自动统计信息重新计算的异步性质,它可能不会立即在运行影响超过10%表行数的DML操作后重新计算统计信息,即使innodb_stats_auto_recalc
变量启用。统计信息重新计算可能会在某些情况下延迟几秒。如果需要立即获取最新的统计信息,请运行ANALYZE TABLE
以启动同步(前台)统计信息重新计算。
如果innodb_stats_auto_recalc
被禁用,您可以通过执行ANALYZE TABLE
语句确保优化器统计的准确性,特别是在对索引列进行了大量更改后。您也可以考虑在加载数据时添加ANALYZE TABLE
到setup脚本中,并在低活动时间段内定期执行ANALYZE TABLE
。
当对现有表添加索引,或者在添加或删除列时,索引统计信息将被计算并添加到innodb_index_stats
表中,不管innodb_stats_auto_recalc
的值是什么。
对于具有AUTO UPDATE启用的histogram(见Histogram Statistics Analysis),自动重新计算持久统计信息也会导致histogram的更新。
17.8.10.1.2 配置单个表的优化器统计参数
innodb_stats_persistent
, innodb_stats_auto_recalc
和innodb_stats_persistent_sample_pages
是全局变量。要 Override 这些系统级设置,配置优化器统计参数为单个表,可以在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;
17.8.10.1.3 InnoDB 优化器统计的采样页面数量配置
优化器使用关于键分布的估算统计信息来选择执行计划中的索引,基于索引的相对选择性。操作如ANALYZE TABLE
会导致InnoDB
从每个表的索引中随机采样页面,以估算索引的基数。这种采样技术称为随机探测。
innodb_stats_persistent_sample_pages
控制采样页面的数量。您可以在运行时调整设置以管理优化器使用的统计信息估算的质量。默认值为20。在遇到以下问题时,考虑修改设置:
-
统计信息不够准确,优化器选择了次优的计划,如
EXPLAIN
输出所示。您可以通过比较索引实际基数(由运行SELECT DISTINCT
在索引列上)与统计信息表中的估算来检查统计信息的准确性。如果确定统计信息不够准确,需要将
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
在计算统计信息时读取未提交的事务数据。在删除表中的行事务未提交的情况下,delete标记记录将在计算行估算和索引统计信息时被排除,这可能会导致其他事务在使用不同隔离级别(除了READ UNCOMMITTED
)操作表时产生非最优执行计划。为了避免这种情况,可以启用innodb_stats_include_delete_marked
以确保delete标记记录在计算持久优化器统计信息时被包含。
当innodb_stats_include_delete_marked
启用时,ANALYZE TABLE
在重新计算统计信息时考虑delete标记记录。
innodb_stats_include_delete_marked
是一个全局设置,影响所有InnoDB
表,并且只适用于持久优化器统计信息。
17.8.10.1.5 InnoDB Persistent Statistics 表
持久统计特性依赖于mysql数据库中的内部管理表,名为innodb_ table_stats
和innodb_index_stats
。这些表在所有安装、升级和从源代码构建过程中自动设置。
Table 17.6 innodb_table_stats 表的列
Column name | Description |
---|---|
database_name |
数据库名称 |
table_name |
表名、分区名或子分区名 |
last_update |
InnoDB最后更新该行的时间戳 |
n_rows |
表中的行数 |
clustered_index_size |
主索引的大小(页) |
sum_of_other_index_sizes |
其他索引(非主索引)的总大小(页) |
Table 17.7 innodb_index_stats 表的列
Column name | Description |
---|---|
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 表和索引统计表包括一个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 表和索引统计表,这使得可以强制特定的查询优化计划或测试备选方案,而不需要修改数据库。如果您手动更新统计信息,请使用FLUSH TABLE
语句来加载更新后的统计信息。tbl_name
持久统计信息被认为是本地信息,因为它们与服务器实例相关。因此,在自动统计信息重新计算时,innodb_table_stats
和innodb_index_stats
表不会被复制。如果您使用ANALYZE TABLE
语句来启动同步统计信息重新计算,语句将被复制(除非您抑制了日志),并在副本上进行重新计算。
17.8.10.1.6 InnoDB Persistent Statistics 表 示例
表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_名
列显示以下类型的统计信息:
-
size
: 当stat_名
=size
,stat_值
列显示索引中的总页数。 -
n_叶子页
: 当stat_名
=n_叶子页
,stat_值
列显示索引中的叶子页数。 -
n_diff_pfx
: 当NN
stat_名
=n_diff_pfx01
,stat_值
列显示索引的第一列中的唯一值数。 当stat_名
=n_diff_pfx02
,stat_值
列显示索引的前两列中的唯一值数,以此类推。 当stat_名
=n_diff_pfx
,NN
stat_描述
列显示一个逗号分隔的索引列列表,该列表用于计数。
为了更好地理解 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
中的数据中也可以确认,其中只有一个唯一值为1
。被计数的列(a
)在结果集中显示在stat_description
列中。 -
当
index_ name
等于PRIMARY
,且stat_name
等于n_diff_pfx02
时,stat_value
为5
,表示索引中的两个列(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将所有非唯一索引都suffix到主键上。因此,对次要索引的n_diff%
行需要四个,以便包括次要索引列(c,d
)和主键列(a,b
)。
-
当
index_name
=i1
和stat_name
=n_diff_pfx01
时,stat_value
为1
c
)中只有一个唯一值。该唯一值在表t1
中的列c
中可以看到,该值为(10
)。被计数的列(c
)在结果集的stat_description
列中显示。 -
当
index_name
=i1
和stat_name
=n_diff_pfx02
时,stat_value
为2
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
中的数据,其中有两个唯一值:(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
中可以通过查看数据来确认:(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_名
=i2uniq
和stat_名
=n_差异_pfx01
时,stat_值
为2
e
)中有两个唯一值。通过查看表t1
中的数据,可以确认列e
中有两个唯一值:(100
)和(200
)。被计数的列(e
)在结果集中显示在stat_描述
列中。 -
当
index_名
=i2uniq
和stat_名
=n_差异_pfx02
时,stat_值
为5
e,f
)中有五个唯一值。通过查看表t1
中的数据,可以确认列e
和f
中有五个唯一值:(100,101
)、(200,102
)、(100,103
)、(200,104
)和(100,105
)。被计数的列(e,f
)在结果集中显示在stat_描述
列中。
可以使用 innodb_index_stats
表来检索表、分区或子分区的索引大小。在以下示例中,索引大小被检索到表 t1
。关于表 t1
和相应索引统计信息,请见第17.8.10.1.6节,“InnoDB Persistent Statistics Tables Example”。
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;