统计信息记录表|全方位认识 mysql 系统库

在上一期 《数据库对象信息记录表|全方位认识 mysql 系统库》中,我们详细介绍了mysql系统库中的元数据记录表,本期我们将为大家带来系列第四篇《统计信息记录表|全方位认识 mysql 系统库》,下面请跟随我们一起开始 mysql 系统库的系统学习之旅吧。

1 | 统计信息概述

如何配置统计信息持久化优化。

  • 持久化统计功能是通过将内存中的统计数据存储到磁盘总,使其在数据库重启时可以快速重新读入这些统计信息而不用重新执行统计,从而使得查询优化器可以利用这些持久化的统计信息准确地选择执行计划(如果没有这些持久化的统计信息,那么数据库重启之后内存中的统计信息将会丢失,下一次访问到某库某表的时候,统计信息需要重新计算,并且重新计算可能会因为估算值的差异导致查询计划发生变更,从而导致查询性能可能发生变化) ,如果启用统计信息的持久化功能呢?当innodb_stats_persistent = ON时或者建表时使用了建表选项STATS_PERSISTENT = 1,则表示开启统计信息的持久化功能(注意,后者表示只开启单表的统计信息持久化且无论innodb_stats_persistent 参数是否启用,前者代表开启全局所有表的统计信息持久化。 innodb_stats_persistent系统变量默认开启,如果要单独关闭某个表的持久化统计信息功能,可以通过语句ALTER TABLE tbl_name STATS_PERSISTENT = 0 来修改)。
  • 持久统计信息存储在mysql.innodb_table_stats和mysql.innodb_index_stats表中,前者存放表结构、数据行相关的统计信息,后者存放索引值相关的统计信息。

如何配置统计信息的持久化优化自动计算。

  • innodb_stats_auto_recalc系统变量控制是否启用统计信息的自动计算功能,默认开启,当自动计算功能开启时,表中的数据量变更超过10%时会触发统计信息自动计算功能。如果innodb_stats_auto_recalc变量未启用,您还可以在CREATE TABLE或ALTER TABLE语句中使用STATS_AUTO_RECALC子句为单个表配置统计信息自动重新计算功能。
  • 自动重新计算在后台运行,所以 即使启用了innodb_stats_auto_recalc系统变量,当表中的数据DML操作超过10%之后,统计信息也可能不会立即重新计算, 某些情况下可能会延迟几秒钟,如果需要统计信息精确,则你可以手动执行ANALYZE TABLE语句来确保优化程序统计信息的准确性。
  • 当某表添加新的索引时,无论系统参数innodb_stats_auto_recalc的值如何,都会触发重新计算索引统计信息并将其添加到innodb_index_stats表中。但要注意,这里说的是会触发重新计算索引统计信息,而不是表mysql.innodb_table_stats表中的表及其数据相关的统计信息,要想在添加索引时数据相关的统计信息同时更新到mysql.innodb_table_stats表中,需要启用系统变量innodb_stats_auto_recalc或者修改表的innodb_stats_auto_recalc建表选项,或者对表执行ANALYZE TABLE语句。

如何配置单个表的统计信息持久化优化。

  • innodb_stats_persistent,innodb_stats_auto_recalc和innodb_stats_persistent_sample_pages是全局的系统变量。如果你有需要忽略全局变量的值而单独指定某个表是否需要配置持久化统计信息,那么可以使用表的建表选项(STATS_PERSISTENT,STATS_AUTO_RECALC和STATS_SAMPLE_PAGES子句)来覆盖系统变量设置的值,建表选项可以在CREATE TABLE或ALTER TABLE语句中指定。

*STATS_PERSISTENT: 指定是否启用InnoDB表的持久统计信息。如果不设置,默认为DEFAULT,表示表的持久统计信息功能设置由innodb_stats_persistent系统变量确定。如果设置为1,则表示启用该表的持久统计信息,如果设置为则表示关闭此表的持久统计信息功能。如果通过CREATE TABLE或ALTER TABLE语句启用持久性统计信息功能,那么将在代表性数据加载到表中后,调用ANALYZE TABLE语句来计算统计信息。

*STATS_AUTO_RECALC:指定是否自动重新计算InnoDB表的持久统计信息。默认值为DEFAULT,表示表的持久统计信息重新计算功能由系统变量innodb_stats_auto_recalc的值确定。当设置为1时表示启用自动重新计算功能。启用之后当表中数据的10%发生变更时会重新计算统计信息。当设置为0,表示关闭表的自动重新计算统计信息功能,要注意的是,如果关闭之后,表的数据发生了较大更改,那么请手动执行ANALYZE TABLE语句来重新计算统计信息。否则有可能造成因为统计信息不精确而导致执行计划不精确。

*STATS_SAMPLE_PAGES:设置估算索引列的基数和其他统计数据时要抽样的索引页数(例如:ANALYZE 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;

如何配置InnoDB优化器统计信息的采样页数。 

  • MySQL查询优化器使用关于索引的键值统计信息来计算索引选择度,根据选择度来选择执行计划的索引。那么这些统计信息是如何得来的呢?例如:当执行ANALYZE TABLE之类的操作时,InnoDB会从表中的每个索引中抽取随机页面来估计索引的基数。(这种技术被称为随机采样) ,采样页的数量由系统参数innodb_stats_persistent_sample_pages设置,默认为20,该变量为动态变量。通常情况下不需要修改,增大该变量置可能导致每次采样时间变长(因为需要读取更多的页),但如果确定默认的采样数量会导致索引统计信息不精确,那么可以尝试逐步增加该系统变量值,直到具有足够精确的统计信息为止。统计信息是否精确可以通过SELECT DISTINCT(index_name)返回的值与mysql.innodb_index_stats持久统计信息表中提供的估计值来进行对比检查。

如何配置在持久统计信息的计算中包括删除标记的记录。

  • 默认情况下,InnoDB在计算统计信息时会读取未提交的数据。对于从表中执行删除行的操作的未提交事务,InnoDB在估算行和索引统计信息时会忽略这些被打上删除标记的记录,所以这可能会导致对该表执行并行查询的其他事务的执行计划并不精确。为了避免这种情况,可以启用系统参数innodb_stats_include_delete_marked来确保InnoDB在计算持久化统计信息时包含被打上删除标记的记录。当启用innodb_stats_include_delete_marked时,执行ANALYZE TABLE语句时会统计被打上删除标记的记录。要注意的是:innodb_stats_include_delete_marked是全局变量,且不能单独设置某个表,innodb_stats_include_delete_marked是在MySQL 5.7.16中引入的。

统计信息持久化依赖于mysql数据库下的表innodb_table_stats和innodb_index_stats,这些表在安装,升级和源代码构建过程中会自动设置。

  • innodb_table_stats和innodb_index_stats表都包含last_update列,表示InnoDB上次更新索引统计信息的时间。
  • innodb_table_stats和innodb_index_stats表是普通表,可以手动执行更新。通过手动更新统计信息的功能,可以强制执行特定的查询优化计划或测试备选计划,而无需修改数据库。要注意:如果手动更新统计信息,需要执行语句FLUSH TABLE tbl_name命令以使MySQL重新加载更新过后的统计信息。
  • 持久性统计信息被视为本地信息,因为它们与实例自身相关。因此innodb_table_stats和innodb_index_stats表的自动统计信息数据变更不会在主备架构之间复制。但如果是手动执行ANALYZE TABLE语句来触发统计信息重新计算,那么该ANALYZE TABLE语句本身会在主备架构之间复制,以在备库启动统计信息的同步重新计算操作(除非在主库操作时设置了set sql_log_bin=0之类的语句关闭了日志记录)。

2 | 统计信息表详解

2.1. innodb_table_stats

该表提供查询表数据相关的统计信息。

下面是该表中存储的信息内容。

root@localhost : test 08:00:46> use mysql
Database changed
root@localhost : mysql 08:01:30> select * from innodb_table_stats where table_name='test'\G
*************************** 1. row ***************************
           database_name: test
              table_name: test
             last_update: 2018-05-24 20:00:50
                  n_rows: 6
    clustered_index_size: 1
sum_of_other_index_sizes: 2
1 row in set (0.00 sec)

表字段含义。

  • database_name:数据库名称。
  • table_name:表名、分区名或子分区名称。
  • last_update:表示InnoDB上次更新此统计信息行的时间戳。
  • n_rows:表中的估算数据记录行数。
  • clustered_index_size:主键索引的大小,以页为单位的估算数值。
  • sum_of_other_index_sizes:其他(非主键)索引的总大小,以页为单位的估算数值。

2.2. innodb_index_stats

该表提供查询索引相关的统计信息。

下面是该表中存储的信息内容。

root@localhost : mysql 08:01:34> select * from innodb_index_stats where table_name='test';
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| database_name | table_name | index_name | last_update | stat_name | stat_value | 
sample_size | stat_description |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| test | test | PRIMARY | 2018-05-24 20:00:50 | n_diff_pfx01 | 5 | 1 | a |
| test | test | PRIMARY | 2018-05-24 20:00:50 | n_diff_pfx02 | 6 | 1 | a,b |
| test | test | PRIMARY | 2018-05-24 20:00:50 | n_leaf_pages | 1 | NULL | Number of leaf pages in the index |
| test | test | PRIMARY | 2018-05-24 20:00:50 | size | 1 | NULL | Number of pages in the index |
| test | test | i1 | 2018-05-24 20:00:50 | n_diff_pfx01 | 5 | 1 | c |
| test | test | i1 | 2018-05-24 20:00:50 | n_diff_pfx02 | 5 | 1 | c,d |
| test | test | i1 | 2018-05-24 20:00:50 | n_diff_pfx03 | 6 | 1 | c,d,a |
| test | test | i1 | 2018-05-24 20:00:50 | n_diff_pfx04 | 6 | 1 | c,d,a,b |
| test | test | i1 | 2018-05-24 20:00:50 | n_leaf_pages | 1 | NULL | Number of leaf pages in the index |
| test | test | i1 | 2018-05-24 20:00:50 | size | 1 | NULL | Number of pages in the index |
| test | test | i2uniq | 2018-05-24 20:00:50 | n_diff_pfx01 | 6 | 1 | e |
| test | test | i2uniq | 2018-05-24 20:00:50 | n_diff_pfx02 | 6 | 1 | e,f |
| test | test | i2uniq | 2018-05-24 20:00:50 | n_leaf_pages | 1 | NULL | Number of leaf pages in the index |
| test | test | i2uniq | 2018-05-24 20:00:50 | size | 1 | NULL | Number of pages in the index |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
14 rows in set (0.00 sec)

表字段含义。

  • database_name:数据库名称。
  • table_name:表名、分区表名、子分区表名称。
  • index_name:索引名称。
  • last_update:表示InnoDB上次更新此统计信息行的时间戳。
  • stat_name:统计信息名称,其对应的统计信息值保存在stat_value列。
  • stat_value:保存统计信息名称stat_name列对应的统计信息值。
  • sample_size:stat_value列中提供的统计信息估计值的采样页数。
  • stat_description:统计信息名称stat_name列中指定的统计信息的说明信息。

从表中查询所得的数据中,我们可以看到:

  • stat_name列一种有如下几种统计值。

*size: 当stat_name为size值时,stat_value列值表示索引中的总页数量。

*n_leaf_pages: 当stat_name为n_leaf_pages值时,stat_value列值显示索引叶子页的数量。

*n_diff_pfxNN: NN代表数字(例如: 01,02等),当stat_name为n_diff_pfxNN时,stat_value列值显示索引的first column(即索引的最前索引列,从索引定义顺序的第一个列开始)列的唯一值数量,例如: 当NN为01时,stat_value列值就表示索引的第一个列的唯一值数量,当NN为02时,stat_value列值就表示索引的第一和第二个列的组合唯一值数量,以此类推。 此外,在stat_name = n_diff_pfxNN的情况下,stat_description列显示一个以逗号分隔的计算索引统计信息列的列表。

  • 从index_name为PRIMARY数据行的stat_description列的描述信息"a,b"中,我们可以看出 ,主键索引的统计信息列实际上就等于定义的索引列数量。
  • 从index_name为i2uniq数据行的stat_description列的描述信息"e,f"中,我们可以看出 ,唯一索引的统计信息列实际上就等于定义的索引列数量。
  • 从index_name为i1数据行的stat_description列的描述信息 "c,d,a,b"中,我们可以看出,普通索引(非唯一的辅助索引)的统计信息列实际上除了定义的索引列之外,还包含了主键列。即对于非唯一索引在该表中记录的统计信息,InnoDB会附加主键列。
  • 注意:MySQL 5.7中系统变量innodb_stats_persistent_sample_pages定义的持久化统计信息采样页为20,这里示例中的sample_size列值为1是因为表中数据量太小,在一个页中已经足够存放,所以实际采样也只使用了1个页,如果数据量够多,则这里显示的值就会为innodb_stats_persistent_sample_pages系统变量指定的值。

PS:我们可以使用该表中的索引信息页数结合系统变量innodb_page_size的值来计算索引的数据大小,如下:

root@localhost : mysql 08:31:14> SELECT SUM(stat_value) pages, index_name, 
SUM(stat_value)*@@innodb_page_size size FROM mysql.innodb_index_stats WHERE 
table_name='dept_emp' AND stat_name = 'size' GROUP BY index_name;                         
+-------+------------+----------+
| pages | index_name | size |
+-------+------------+----------+
| 737 | PRIMARY | 12075008 |
| 353 | dept_no | 5783552 |
| 353 | emp_no | 5783552 |
+-------+------------+----------+
3 rows in set (0.01 sec)

本期内容就介绍到这里,本期内容参考链接如下:

https://dev.mysql.com/doc/refman/5.7/en/innodb-persistent-stats.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-persistent-stats.html

"翻过这座山,你就可以看到一片海! "。 坚持阅读我们的"全方位认识 mysql 系统库"系列文章分享,你就可以系统地学完它。 谢谢你的阅读,我们下期不见不散!

发表评论

电子邮件地址不会被公开。 必填项已用*标注