持久化优化器统计功能通过将统计信息存储到磁盘并在服务器重启之间保持持久性来提高 计划稳定性,因此 优化器 更有可能在每次针对给定查询时做出一致的选择。
当 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: 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;