持久优化器统计功能通过将统计信息存储到磁盘并使其在服务器重启后保持不变,从而提高了计划稳定性,使优化器在每次查询时都能作出一致的选择。
优化器统计信息将在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
,默认情况下启用,控制着统计信息是否在表发生变化时自动计算。您也可以通过在创建或修改表时指定STATS_AUTO_RECALC
子句来配置自动统计信息重新计算。
由于自动统计信息重新计算的异步性质,在后台进行,即使innodb_stats_auto_recalc
启用,统计信息也可能不会立即重新计算,即使在执行了影响表中10%以上行的DML操作后。统计信息重新计算可能会延迟几秒钟。如果需要立即获得最新的统计信息,请运行ANALYZE TABLE
以启动同步(前台)重新计算统计信息。
如果innodb_stats_auto_recalc
禁用,您可以通过执行ANALYZE TABLE
语句来确保优化器统计信息的准确性。在加载数据后,您也可以将ANALYZE TABLE
添加到设置脚本中,并在低活动期间计划执行ANALYZE TABLE
。
当添加索引到现有表中或添加或删除列时,索引统计信息将被计算并添加到innodb_index_stats
表中,不管innodb_stats_auto_recalc
的值如何。
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
在表数据发生变化时重新计算统计信息,而值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 列
Column name | Description |
---|---|
database_name |
数据库名称 |
table_name |
表名、分区名或子分区名 |
last_update |
最后一次更新的时间戳 |
n_rows |
表中的行数 |
clustered_index_size |
主索引的大小(以页为单位) |
sum_of_other_index_sizes |
其他(非主)索引的总大小(以页为单位) |
表 17.7 innodb_index_stats 列
Column name | Description |
---|---|
database_name |
数据库名称 |
表名 |
表名、分区名或子分区名 |
索引名 |
索引名 |
最后更新 |
一个时间戳,指示行最后一次更新的时间 |
统计名 |
统计名,whose value is reported in the stat_value column |
统计值 |
统计值,named in the stat_name column |
样本大小 |
用于估算的页面样本数,reported in the stat_value column |
统计描述 |
统计描述,named in the stat_name column |
The innodb_table_stats
and innodb_index_stats
表包括一个 最后更新
列,显示索引统计信息最后更新的时间:
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
...
The innodb_table_stats
and innodb_index_stats
表可以手动更新,这使得可以强制特定的查询优化计划或测试替代计划,而不需要修改数据库。如果您手动更新统计信息,请使用 FLUSH TABLE
语句加载更新的统计信息。tbl_name
持久统计信息被认为是本地信息,因为它们与服务器实例相关。因此,innodb_table_stats
和 innodb_index_stats
表在自动统计信息重新计算时不会被复制。如果您运行 ANALYZE TABLE
语句来启动同步重新计算统计信息,该语句将被复制(除非您抑制了日志记录),并在副本上重新计算。
The 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
The innodb_index_stats
表包含每个索引的多行。每行在 innodb_index_stats
表提供了与特定索引统计信息相关的数据,named in the stat_name
column and described in the stat_description
column。例如:
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 |
+------------+--------------+------------+-----------------------------------+
The stat_name
列显示以下类型的统计信息:
-
size
: Wherestat_name
=size
, thestat_value
列显示索引的总页数。 -
n_leaf_pages
: Wherestat_name
=n_leaf_pages
, thestat_value
列显示索引的叶页数。 -
n_diff_pfx
: 在NN
stat_name
=n_diff_pfx01
时,stat_value
列显示索引的第一列中的distinct值的数量。在stat_name
=n_diff_pfx02
时,stat_value
列显示索引的前两列中的distinct值的数量,以此类推。在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
)中有一个distinct值。查看表t1
中的列a
数据,可以确认只有一个distinct值(1
)。计数列(a
)显示在结果集的stat_description
列中。 -
在
index_name
=PRIMARY
和stat_name
=n_diff_pfx02
时,stat_value
是5
,表示索引的前两列(a,b
)中有五个distinct值。查看表t1
中的列a
和b
数据,可以确认有五个distinct值:(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
)中有一个distinct值。查看表t1
中的列c
数据,可以确认只有一个distinct值:(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
) 中有两个不同的值。通过查看表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
) 中有五个不同的值。通过查看表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
) 中有两个不同的值。通过查看表t1
中的列e
的数据,可以确认这两个不同的值: (100
) 和 (200
)。计数列 (e
) 显示在结果集的stat_description
列中。 -
其中
index_name
=i2uniq
且stat_name
=n_diff_pfx02
,stat_value
是5
, 表示索引的两个列 (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;