#### 15.8.10.1 配置持久优化器统计参数

[](<>)[](<>)[](<>)[](<>)[](<>)

持久优化器统计功能改进了[计划稳定性](glossary.html#glos_plan_stability)通过将统计信息存储到磁盘并使它们在服务器重新启动时保持不变，以便[优化器](glossary.html#glos_optimizer)对于给定的查询，每次都更有可能做出一致的选择。

优化器统计信息会在以下情况下持久保存到磁盘[`innodb_stats_persistent=ON`](innodb-parameters.html#sysvar_innodb_stats_persistent)或者当单个表定义为[`STATS_PERSISTENT=1`](create-table.html).[`innodb_stats_persistent`](innodb-parameters.html#sysvar_innodb_stats_persistent)默认启用。

以前，优化器统计信息在重新启动服务器时和一些其他类型的操作后被清除，并在下一次表访问时重新计算。因此，在重新计算统计信息时可能会产生不同的估计，从而导致查询执行计划中的不同选择和查询性能的变化。

持久统计信息存储在`mysql.innodb_table_stats`和`mysql.innodb_index_stats`表。看[第 15.8.10.1.5 节，“InnoDB 持久统计表”](innodb-persistent-stats.html#innodb-persistent-stats-tables).

如果您不想将优化器统计信息保存到磁盘，请参阅[第 15.8.10.2 节，“配置非持久优化器统计参数”](innodb-statistics-estimation.html)

##### 15.8.10.1.1 为持久优化器配置自动统计计算

统计数据

这[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc)默认情况下启用的变量控制当表对其行的更改超过 10% 时是否自动计算统计信息。您还可以通过指定`STATS_AUTO_RECALC`创建或更改表时的子句。

由于在后台发生的自动统计重新计算的异步特性，在运行影响超过 10% 表的 DML 操作后，统计可能不会立即重新计算，即使在[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc)已启用。在某些情况下，统计数据重新计算可能会延迟几秒钟。如果立即需要最新的统计信息，请运行[`分析表`](analyze-table.html)启动统计数据的同步（前台）重新计算。

如果[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc)被禁用，您可以通过执行[`分析表`](analyze-table.html)对索引列进行重大更改后的语句。您也可以考虑添加[`分析表`](analyze-table.html)设置加载数据后运行的脚本，并运行[`分析表`](analyze-table.html)在活动少的时候按计划进行。

将索引添加到现有表时，或者添加或删除列时，会计算索引统计信息并将其添加到`innodb_index_stats`表不管的值[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc).

##### 15.8.10.1.2 为单个表配置优化器统计参数

[`innodb_stats_persistent`](innodb-parameters.html#sysvar_innodb_stats_persistent),[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc)， 和[`innodb_stats_persistent_sample_pages`](innodb-parameters.html#sysvar_innodb_stats_persistent_sample_pages)是全局变量。要覆盖这些系统范围的设置并为各个表配置优化器统计参数，您可以定义`STATS_PERSISTENT`,`STATS_AUTO_RECALC`， 和`STATS_SAMPLE_PAGES`中的子句[`创建表`](create-table.html)要么[`更改表`](alter-table.html)陈述。

-   `STATS_PERSISTENT`指定是否启用[持续统计](glossary.html#glos_persistent_statistics)为`InnoDB`桌子。价值`默认`导致表的持久统计信息设置由[`innodb_stats_persistent`](innodb-parameters.html#sysvar_innodb_stats_persistent)环境。一个值`1`启用表的持久统计信息，而值为`0`禁用该功能。为单个表启用持久统计信息后，使用[`分析表`](analyze-table.html)加载表数据后计算统计信息。

-   `STATS_AUTO_RECALC`指定是否自动重新计算[持续统计](glossary.html#glos_persistent_statistics).价值`默认`导致表的持久统计信息设置由[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_innodb_stats_auto_recalc)环境。一个值`1`当 10% 的表数据发生更改时，会导致重新计算统计信息。一个值`0`防止自动重新计算表。当使用值 0 时，使用[`分析表`](analyze-table.html)在对表进行重大更改后重新计算统计信息。

-   `STATS_SAMPLE_PAGES`指定在为索引列计算基数和其他统计信息时要采样的索引页数，由[`分析表`](analyze-table.html)操作，例如。

    所有三个子句都在下面指定[`创建表`](create-table.html)例子：


```
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;
```

##### 15.8.10.1.3 配置 InnoDB 优化器统计的采样页数

优化器使用估计的[统计数据](glossary.html#glos_statistics)关于键分布来选择执行计划的索引，基于相对[选择性](glossary.html#glos_selectivity)的指数。操作如[`分析表`](analyze-table.html)原因`InnoDB`从表上的每个索引中随机抽取页面以估计[基数](glossary.html#glos_cardinality)的指数。这种采样技术被称为[随机潜水](glossary.html#glos_random_dive).

这[`innodb_stats_persistent_sample_pages`](innodb-parameters.html#sysvar_innodb_stats_persistent_sample_pages)控制采样页面的数量。您可以在运行时调整设置以管理优化器使用的统计估计质量。默认值为 20。遇到以下问题时，请考虑修改设置：

1.  *统计数据不够准确，优化器选择次优计划*，如图所示[`解释`](explain.html)输出。您可以通过比较索引的实际基数（通过运行确定）来检查统计信息的准确性[`选择不同的`](select.html)在索引列上）与在`mysql.innodb_index_stats`桌子。

    如果确定统计数据不够准确，则[`innodb_stats_persistent_sample_pages`](innodb-parameters.html#sysvar_innodb_stats_persistent_sample_pages)应增加，直到统计估计足够准确。增加[`innodb_stats_persistent_sample_pages`](innodb-parameters.html#sysvar_innodb_stats_persistent_sample_pages)然而，过多可能会导致[`分析表`](analyze-table.html)慢慢地跑。

2.  *[`分析表`](analyze-table.html)太慢了*.在这种情况下[`innodb_stats_persistent_sample_pages`](innodb-parameters.html#sysvar_innodb_stats_persistent_sample_pages)应该减少直到[`分析表`](analyze-table.html)执行时间是可以接受的。但是，过多地减小该值可能会导致第一个问题，即统计信息不准确和查询执行计划不理想。

    如果无法在准确的统计数据和[`分析表`](analyze-table.html)执行时间，考虑减少表中的索引列数或限制分区数以减少[`分析表`](analyze-table.html)复杂。表的主键中的列数也很重要，因为主键列附加到每个非唯一索引。

    有关相关信息，请参阅[第 15.8.10.3 节，“估计 InnoDB 表的分析表复杂性”](innodb-analyze-table-complexity.html).

##### 15.8.10.1.4包括删除持久性统计计算中的标记记录

[](<>)[](<>)

默认情况下，`InnoDB`计算统计信息时读取未提交的数据。对于从表中删除行的未提交事务，在计算行估计和索引统计信息时，删除标记的记录被排除在外，这可能会导致其他事务的非最佳执行计划，这些事务在表上同时使用除[`阅读未提交`](innodb-transaction-isolation-levels.html#isolevel_read-uncommitted).为了避免这种情况，[`innodb_统计数据_包含_删除_标记`](innodb-parameters.html#sysvar_innodb_stats_include_delete_marked)可以启用，以确保在计算持久性优化器统计信息时包含删除标记的记录。

什么时候[`innodb_统计数据_包含_删除_标记`](innodb-parameters.html#sysvar_innodb_stats_include_delete_marked)已启用，[`分析表`](analyze-table.html)在重新计算统计信息时考虑删除标记的记录。

[`innodb_统计数据_包含_删除_标记`](innodb-parameters.html#sysvar_innodb_stats_include_delete_marked)是一个影响所有人的全球环境`InnoDB`表，它仅适用于持久性优化器统计信息。

##### 15.8.10.1.5 InnoDB持久性统计表

持久性统计特性依赖于`mysql`数据库，名为`innodb_表_统计数据`和`innodb_索引_统计数据`。这些表是在所有安装、升级和从源代码生成过程中自动设置的。

[](<>)

**表15.6 innodb的列数\_桌子\_统计数据**

| 列名 | 描述 |
| --- | --- |
| `数据库名称` | 数据库名称 |
| `表2\u名称` | 表名、分区名或子分区名 |
| `最后更新` | 一个时间戳，指示最后一次`InnoDB`更新了这一行 |
| `n_行` | 表中的行数 |
| `聚集索引大小` | 主索引的大小，以页为单位 |
| `其他索引大小之和` | 其他（非主）索引的总大小，以页为单位 |

[](<>)

**表15.7 innodb的列数\_指数\_统计数据**

| 列名 | 描述 |
| --- | --- |
| `数据库名称` | 数据库名称 |
| `表2\u名称` | 表名、分区名或子分区名 |
| `索引名称` | 索引名 |
| `最后更新` | 指示行上次更新时间的时间戳 |
| `统计名称` | 统计信息的名称，其值在`统计值`柱 |
| `统计值` | 中命名的统计信息的值`统计名称`柱 |
| `样本量` | 中提供的估算抽样页数`统计值`柱 |
| `统计描述` | 中命名的统计信息的描述`统计名称`柱 |

这个`innodb_表_统计数据`和`innodb_索引_统计数据`表格包括`最后更新`显示上次更新索引统计信息的时间的列：

```
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_表_统计数据`和`innodb_索引_统计数据`可以手动更新表，这样就可以在不修改数据库的情况下强制执行特定的查询优化计划或测试替代计划。如果手动更新统计信息，请使用`冲水桌*`tbl\_名称`*`语句来加载更新的统计信息。

持久性统计信息被视为本地信息，因为它们与服务器实例相关。这个`innodb_表_统计数据`和`innodb_索引_统计数据`因此，在自动重新计算统计数据时，不会复制表。如果你跑[`分析表`](analyze-table.html)要启动统计数据的同步重新计算，将复制该语句（除非禁止对其进行日志记录），并在副本上进行重新计算。

##### 15.8.10.1.6 InnoDB持久性统计表示例

这个`innodb_表_统计数据`每个表包含一行。下面的示例演示了收集的数据类型。

桌子`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.html)（如果[`innodb_stats_auto_recalc`](innodb-parameters.html#sysvar_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_索引_统计数据`表包含每个索引的多行。表中的每一行`innodb_索引_统计数据`表提供了与中指定的特定索引统计信息相关的数据`统计名称`列，并在`统计描述`柱例如：

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

这个`统计名称`列显示以下类型的统计信息：

-   `大小`：在哪里`统计名称`=`大小`这个`统计值`列显示索引中的总页数。

-   `n_leaf_页面`：在哪里`统计名称`=`n_leaf_页面`这个`统计值`列显示索引中的叶页数。

-   `n_diff_pfx*`NN`*`：在哪里`统计名称`=`n_diff_pfx01`这个`统计值`列显示索引第一列中不同值的数量。哪里`统计名称`=`n_diff_pfx02`这个`统计值`列显示索引前两列中不同值的数量，依此类推。哪里`统计名称`=`n_diff_pfx*`NN`*`这个`统计描述`列显示以逗号分隔的已计数索引列列表。

    为了进一步说明`n_diff_pfx*`NN`*`提供基数数据的统计数据再次考虑`t1`表1是前面介绍的示例。如下图所示`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 |
+---+---+------+------+------+------+
```

当你询问`索引名称`, `统计名称`, `统计值`和`统计描述`哪里`统计名称，如“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              |
+------------+--------------+------------+------------------+
```

对于`主要的，重要的`索引，有两个`n_差异%`排。行数等于索引中的列数。

笔记

对于非唯一索引，`InnoDB`追加主键的列。

-   哪里`索引名称`=`主要的，重要的`和`统计名称`=`n_diff_pfx01`这个`统计值`是`1.`，这表示索引的第一列（第列）中有一个单独的值`A.`).列中不同值的数目`A.`通过查看列中的数据来确认`A.`在桌子上`t1`，其中只有一个不同的值(`1.`).计数列(`A.`)如图所示`统计描述`结果集的列。

-   哪里`索引名称`=`主要的，重要的`和`统计名称`=`n_diff_pfx02`这个`统计值`是`5.`，这表明索引的两列中有五个不同的值(`a、 b`).列中不同值的数量`A.`和`b`通过查看列中的数据来确认`A.`和`b`在桌子上`t1`，其中有五个不同的值：(`1,1`), (`1,2`), (`1,3`), (`1,4`)及(`1,5`).数列(`a、 b`)如图所示`统计描述`结果集的列。

    二级索引(`i1`)，有四个`n_差异%`排。二级索引只定义了两列(`c、 d`)但是有四个`n_差异%`二级索引的行，因为`InnoDB`用主键作为所有非唯一索引的后缀。因此，有四个`n_差异%`行而不是两行，以说明二级索引列的(`c、 d`)以及主键列(`a、 b`).

-   哪里`索引名称`=`i1`和`统计名称`=`n_diff_pfx01`这个`统计值`是`1.`，这表示索引的第一列（第列）中有一个单独的值`c`).列中不同值的数目`c`通过查看列中的数据来确认`c`在桌子上`t1`，其中有一个单独的值：(`10`).计数列(`c`)如图所示`统计描述`结果集的列。

-   哪里`索引名称`=`i1`和`统计名称`=`n_diff_pfx02`这个`统计值`是`2.`，这表示索引的前两列中有两个不同的值(`c、 d`).列中不同值的数量`c`一`d`通过查看列中的数据来确认`c`和`d`在桌子上`t1`，其中有两个不同的值：(`10,11`)及(`10,12`).数列(`c、 d`)如图所示`统计描述`结果集的列。

-   哪里`索引名称`=`i1`和`统计名称`=`n_diff_pfx03`这个`统计值`是`2.`，这表明索引的前三列中有两个不同的值(`c、 d，a`).列中不同值的数量`c`, `d`和`A.`通过查看列中的数据来确认`c`, `d`和`A.`在桌子上`t1`，其中有两个不同的值：(`10,11,1`)及(`10,12,1`).数列(`c、 d，a`)如图所示`统计描述`结果集的列。

-   哪里`索引名称`=`i1`和`统计名称`=`n_diff_pfx04`这个`统计值`是`5.`，这表明索引的四列中有五个不同的值(`c、 d，a，b`).列中不同值的数量`c`, `d`, `A.`和`b`通过查看列中的数据来确认`c`, `d`, `A.`和`b`在桌子上`t1`，其中有五个不同的值：(`10,11,1,1`), (`10,11,1,2`), (`10,11,1,3`), (`10,12,1,4`)，及(`10,12,1,5`).数列(`c、 d，a，b`)如图所示`统计描述`结果集的列。

    对于唯一索引(`i2uniq`)有两种`n_差异%`排。

-   哪里`索引名称`=`i2uniq`和`统计名称`=`n_diff_pfx01`这个`统计值`是`2.`，这表示索引的第一列（第列）中有两个不同的值`e`).列中不同值的数目`e`通过查看列中的数据来确认`e`在桌子上`t1`，其中有两个不同的值：(`100`)及(`200`).计数列(`e`)如图所示`统计描述`结果集的列。

-   哪里`索引名称`=`i2uniq`和`统计名称`=`n_diff_pfx02`这个`统计值`是`5.`，这表明索引的两列中有五个不同的值(`e、 f`).列中不同值的数量`e`和`f`通过查看列中的数据来确认`e`和`f`在桌子上`t1`，其中有五个不同的值：(`100,101`), (`200,102`), (`100,103`), (`200,104`)，及(`100,105`).数列(`e、 f`)如图所示`统计描述`结果集的列。

##### 15.8.10.1.7使用innodb检索索引大小\_指数\_统计表

可以使用`innodb_索引_统计数据`桌子在下面的示例中，检索表的索引大小`t1`.了解表格的定义`t1`以及相应的指数统计，请参见[第15.8.10.1.6节，“InnoDB持久统计表示例”](innodb-persistent-stats.html#innodb-persistent-stats-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 |
+-------+------------+-------+
```

对于分区或子分区，可以将同一查询与修改的`哪里`子句来检索索引大小。例如，以下查询检索表的分区的索引大小`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;
```
