Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  Configuring Persistent Optimizer Statistics Parameters

17.8.10.1 配置持久优化器统计参数

持久优化器统计功能通过将统计信息存储到磁盘并使其在服务器重启后保持不变,从而提高了计划稳定性,使优化器在每次查询时都能作出一致的选择。

优化器统计信息将在innodb_stats_persistent=ON或在单个表上定义STATS_PERSISTENT=1时被持久化到磁盘。innodb_stats_persistent默认情况下是启用的。

以前,优化器统计信息在服务器重启和其他一些操作后将被清除,并在下一次表访问时重新计算,从而导致不同的估算值和查询性能的变化。

持久统计信息存储在mysql.innodb_table_statsmysql.innodb_index_stats表中。见第17.8.10.1.5节,“InnoDB持久统计表”

如果您不想将优化器统计信息持久化到磁盘,请参阅第17.8.10.2节,“配置非持久优化器统计参数”

17.8.10.1 配置持久优化器统计参数

变量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的值如何。

17.8.10.1.2 配置单个表的优化器统计参数

innodb_stats_persistentinnodb_stats_auto_recalcinnodb_stats_persistent_sample_pages 是全局变量。要覆盖这些系统范围的设置并配置单个表的优化器统计参数,可以在 CREATE TABLEALTER TABLE 语句中定义 STATS_PERSISTENTSTATS_AUTO_RECALCSTATS_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;
17.8.10.1.3 配置 InnoDB 优化器统计信息的采样页数

优化器使用关于键分布的估算统计信息来选择执行计划的索引,基于索引的相对 选择性。操作如 ANALYZE TABLE 导致 InnoDB 从每个索引的表中随机采样页,以估算索引的基数。这项技术称为 随机潜水

控制采样页数的设置是 innodb_stats_persistent_sample_pages。您可以在运行时调整该设置以管理优化器使用的统计信息估算的质量。默认值为 20。考虑在遇到以下问题时修改该设置:

  1. 统计信息不够准确,优化器选择了次优的计划,如 EXPLAIN 输出所示。您可以通过比较索引的实际基数(通过运行 SELECT DISTINCT 在索引列上)与 mysql.innodb_index_stats 表中的估算值来检查统计信息的准确性。

    如果确定统计数据不够准确,应该增加innodb_stats_persistent_sample_pages的值,直到统计估算足够准确。然而,增加innodb_stats_persistent_sample_pages太多可能会导致ANALYZE TABLE运行缓慢。

  2. ANALYZE TABLE 运行太慢。在这种情况下,innodb_stats_persistent_sample_pages应该减少,直到ANALYZE TABLE执行时间可接受。减少该值太多,然而,可能会导致第一个问题,即统计数据不准确和次优查询执行计划。

    如果无法在准确的统计数据和ANALYZE TABLE执行时间之间找到平衡,考虑减少表中的索引列数或限制分区数,以减少ANALYZE TABLE的复杂度。表的主键列数也很重要,因为主键列被追加到每个非唯一索引中。

    有关信息,请参阅第 17.8.10.3 节,“估算 InnoDB 表的 ANALYZE TABLE 复杂度”

17.8.10.1.4 在持久统计计算中包括删除标记的记录

默认情况下,InnoDB 在计算统计数据时读取未提交的数据。在未提交的事务中删除表行时,删除标记的记录将被排除在统计估算和索引统计之外,这可能会导致其他事务在并发操作表时使用非最优执行计划,除非事务隔离级别为READ UNCOMMITTED。为了避免这种情况,可以启用innodb_stats_include_delete_marked,以确保删除标记的记录被包括在持久优化器统计中。

innodb_stats_include_delete_marked启用时,ANALYZE TABLE 将考虑删除标记的记录时重新计算统计数据。

innodb_stats_include_delete_marked 是一个全局设置,影响所有 InnoDB 表,并且仅适用于持久优化器统计。

17.8.10.1.5 InnoDB 持久统计表

持久统计功能依赖于 mysql 数据库中的内部管理表,名为 innodb_table_statsinnodb_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_statsinnodb_index_stats 表在自动统计信息重新计算时不会被复制。如果您运行 ANALYZE TABLE 语句来启动同步重新计算统计信息,该语句将被复制(除非您抑制了日志记录),并在副本上重新计算。

17.8.10.1.6 InnoDB 持久统计信息表示例

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: Where stat_name=size, the stat_value 列显示索引的总页数。

  • n_leaf_pages: Where stat_name=n_leaf_pages, the stat_value 列显示索引的叶页数。

  • n_diff_pfxNN: 在 stat_name = n_diff_pfx01 时,stat_value 列显示索引的第一列中的distinct值的数量。在 stat_name = n_diff_pfx02 时,stat_value 列显示索引的前两列中的distinct值的数量,以此类推。在 stat_name = n_diff_pfxNN 时,stat_description 列显示索引列的逗号分隔列表,这些列被计数。

为了进一步说明 n_diff_pfxNN 统计信息,它提供了基数数据,考虑之前引入的 t1 表示例。如下所示,t1 表创建了一个主索引(列 ab),一个次索引(列 cd),和一个唯一索引(列 ef):

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_namestat_namestat_valuestat_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% 行。行数等于索引中的列数。

Note

对于非唯一索引,InnoDB 附加主键的列。

  • index_name = PRIMARYstat_name = n_diff_pfx01 时,stat_value1,表示索引的第一列(列 a)中有一个distinct值。查看表 t1 中的列 a 数据,可以确认只有一个distinct值(1)。计数列(a)显示在结果集的 stat_description 列中。

  • index_name = PRIMARYstat_name = n_diff_pfx02 时,stat_value5,表示索引的前两列(a,b)中有五个distinct值。查看表 t1 中的列 ab 数据,可以确认有五个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 = i1stat_name = n_diff_pfx01 时,stat_value1,表示索引的第一列(列 c)中有一个distinct值。查看表 t1 中的列 c 数据,可以确认只有一个distinct值:(10)。计数列(c)显示在结果集的 stat_description 列中。

  • 其中 index_name = i1stat_name = n_diff_pfx02, stat_value2, 表示索引的前两个列 (c,d) 中有两个不同的值。通过查看表 t1 中的列 cd 的数据,可以确认这两个不同的值: (10,11) 和 (10,12)。计数列 (c,d) 显示在结果集的 stat_description 列中。

  • 其中 index_name = i1stat_name = n_diff_pfx03, stat_value2, 表示索引的前三个列 (c,d,a) 中有两个不同的值。通过查看表 t1 中的列 cda 的数据,可以确认这两个不同的值: (10,11,1) 和 (10,12,1)。计数列 (c,d,a) 显示在结果集的 stat_description 列中。

  • 其中 index_name = i1stat_name = n_diff_pfx04, stat_value5, 表示索引的四个列 (c,d,a,b) 中有五个不同的值。通过查看表 t1 中的列 cdab 的数据,可以确认这五个不同的值: (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 = i2uniqstat_name = n_diff_pfx01, stat_value2, 表示索引的第一个列 (e) 中有两个不同的值。通过查看表 t1 中的列 e 的数据,可以确认这两个不同的值: (100) 和 (200)。计数列 (e) 显示在结果集的 stat_description 列中。

  • 其中 index_name = i2uniqstat_name = n_diff_pfx02, stat_value5, 表示索引的两个列 (e,f) 中有五个不同的值。通过查看表 t1 中的列 ef 的数据,可以确认这五个不同的值: (100,101), (200,102), (100,103), (200,104), 和 (100,105)。计数列 (e,f) 显示在结果集的 stat_description 列中。

17.8.10.1.7 使用 innodb_index_stats 表检索索引大小

您可以使用 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;