文档首页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  配置持久化优化器统计参数

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.1 配置持久化优化器统计信息的自动统计计算

变量 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 的直方图(请参阅 直方图统计分析),自动重新计算持久性统计信息也会导致直方图更新。

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 导致当 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。在遇到以下问题时,请考虑修改设置

  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 的列

列名 说明
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_statsinnodb_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_statsinnodb_index_stats 表可以手动更新,这使得可以在不修改数据库的情况下强制执行特定的查询优化计划或测试备选计划。如果您手动更新统计信息,请使用 FLUSH TABLE tbl_name 语句来加载更新后的统计信息。

持久统计信息被视为本地信息,因为它们与服务器实例相关。因此,当自动统计信息重新计算发生时,innodb_table_statsinnodb_index_stats 表不会被复制。如果您运行 ANALYZE TABLE 以启动统计信息的同步重新计算,则该语句将被复制(除非您抑制了对它的日志记录),并且重新计算将在副本上进行。

17.8.10.1.6 InnoDB 持久统计信息表示例

innodb_table_stats 表为每个表包含一行。以下示例演示了收集的数据类型。

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 |
+---+---+------+------+------+------+

要立即更新统计信息,请运行 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_pfxNN:当 stat_name=n_diff_pfx01 时,stat_value 列显示索引第一列中不同值的个数。当 stat_name=n_diff_pfx02 时,stat_value 列显示索引前两列中不同值的个数,依此类推。当 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% 行。行数等于索引中列数。

注意

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

  • index_name=PRIMARYstat_name=n_diff_pfx01 的情况下,stat_value1,这表明索引的第一列(a 列)中只有一个不同的值。通过查看 t1 表中 a 列的数据可以确认 a 列中不同值的个数,其中只有一个不同的值 (1)。计数列 (a) 显示在结果集的 stat_description 列中。

  • index_name=PRIMARYstat_name=n_diff_pfx02 的情况下,stat_value5,这表明索引的两列 (a,b) 中有五个不同的值。通过查看 t1 表中 ab 列的数据可以确认 ab 列中不同值的个数,其中有五个不同的值: (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 列)中只有一个不同的值。通过查看 t1 表中 c 列的数据可以确认 c 列中不同值的个数,其中只有一个不同的值: (10)。计数列 (c) 显示在结果集的 stat_description 列中。

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

  • index_name=i1stat_name=n_diff_pfx03 的情况下,stat_value2,这表明索引的前三列 (c,d,a) 中有两个不同的值。通过查看 t1 表中 cda 列的数据可以确认 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 列的数据可以确认 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 列的数据可以确认 e 列中不同值的个数,其中有两个不同的值: (100) 和 (200)。计数列 (e) 显示在结果集的 stat_description 列中。

  • index_name=i2uniqstat_name=n_diff_pfx02 的情况下,stat_value5,这表明索引的两列 (e,f) 中有五个不同的值。通过查看 t1 表中 ef 列的数据可以确认 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;