第13期:表统计信息的计算
本篇介绍 MySQL 表如何计算统计信息。表统计信息是数据库基于成本的优化器最重要的参考信息;统计信息不准确,优化器可能给出不够优化的执行计划或者是错误的执行计划。
对统计信息的计算分为非持久化统计信息(实时计算)与持久化统计信息。
非持久化统计信息
- 统计信息没有保存在磁盘上,而是频繁的实时计算统计信息;
- 每次对表的访问都会重新计算其统计信息;
假设针对一张大表的频繁查询,那么每次都要重新计算统计信息,很耗费资源。
持久化统计信息
- 把一张表在某一时刻的统计信息值保存在磁盘上;
- 避免每次查询时重新计算;
- 如果表更新不是很频繁,或者没有达到 MySQL 必须重新计算统计信息的临界值,可直接从磁盘上获取;
- 即使 MySQL 服务重启,也可以快速的获取统计信息值;
- 统计信息的持久化可以针对全局设置也可以针对单表设置。
接下来,详细说 MySQL 统计信息如何计算,何时计算,效果评估等问题。
在 MySQL Server 层来控制是否自动计算统计信息的分布,并且来决策是持久化还是非持久化。
一、持久化统计相关参数:
- innodb_stats_persistent :是否开启统计信息持久化,默认开启。
- innodb_stats_auto_recalc :是否自动重新计算持久化统计信息,默认开启。
二、具体的更新策略为:
当一张表数据变化超过 10% 后,MySQL 会针对这张表统计信息的更新时间戳做一个判断,检查最后一次更新的时间是否超过 10 秒;如果不到 10 秒,把这张表加到一个统计信息更新队列中,到时间了再重新计算;如果超过了 10 秒,直接重新计算,并且更新时间戳。
目前这个超时时间写死在 MySQL 代码里,暂时不能更改。不过在某些 MySQL 分支版还可以控制这个时间,比如 Percona。
- innodb_stats_include_delete_marked :更新持久化统计信息时,是否会计算已经标记为删除的行。 默认是关闭的,会获取未提交的脏数据。开启这个选项,MySQL 计算统计信息时只会考虑已经提交的数据。
- innodb_stats_persistent_sample_pages :用于更新持久化索引分布或者其他统计信息的随机基数页,默认 20 个。 页数越多,统计信息也就越准确,也就有助于查询优化器选择最优的查询计划。
什么时候考虑更改这个值呢?
- 当查询计划不是很准确时。比如对比指定表在系统表 mysql.innodb_index_stats 的数据跟 distinct 查询的结果,如果相差太大,可以考虑增加这个值。
- 当 analyze table 变的非常慢时,可能是这个值设置的太大了,此时要考虑减小这个值。
三、非持久化统计信息参数
- innodb_stats_transient_sample_pages:设置非持久化统计信息的采样页数目,默认 8 个。
- innodb_stats_on_metadata:当统计信息配置为非持久化时生效,默认关闭。 参数 innodb_stats_persistent 为 0 或者建表时属性 STATS_PERSISTENT=0 才起作用。
当开启后,对以下元数据的访问会自动更新统计信息:
- show table status
- show index
- information_schema.tables
- information_schema.statistics
所以开启这个选项会额外增加访问表的开销,特别是大表。
还有一些其他的场景会自动更新非持久化统计信息,比如:
- 表第一次被访问;
- InnoDB 检测到有十六分之一的表自从上次统计信息计算后被更新了,这时触发自动更新;
- MySQL 客户端默认选项 --auto-rehash 打开所有 InnoDB 表,导致所有 InnoDB 表被自动更新统计信息;
四、表属性控制
STATS_AUTO_RECALC
用来指定是否要自动计算指定 InnoDB 表的统计信息。
三个值:default, 0, 1
- default:也就是默认值,依赖 server 端参数 innodb_stats_auto_recalc 的设置效果
- 0:表示禁用统计信息的自动重新计算,也就是永远不重新计算,需要手动执行 analyze table
- 1:表示当表数据有 10% 的数据变化后,则重新计算持久化统计信息。
STATS_PERSISTENT
用来指定是否要开启指定 InnoDB 表的统计信息持久化。
三个值:default, 0, 1
- default:依赖 server 端参数 innodb_stats_persistent 的设置
- 0:表示不需要持久化统计信息
- 1:表示开启持久化统计信息
STATS_SAMPLE_PAGES
用来指定计算统计信息时的采样页数量。
五、手动更新统计信息
analyze table 用来手动更新表统计信息。建议在业务低峰时执行。
六、持久化表统计元数据信息
优化器通过两张元数据表里的数据来决定查询最优执行计划。
表统计信息保存在表 mysql.innodb_table_stats 里
比如表 ytt_sample_persist 的统计信息
重要列说明:
- n_rows:表的行数
- clustered_index_size:主键的数据页个数
- sum_of_other_index_sizes:二级索引的数据页个数
以下例子可以看到表 ytt_sample_persist 表行数大概为 36W 行,聚簇索引页数为 15162,二级索引页数为 4113。
这些值都是基于采样页来计算的,所以是一个预估值。
mysql> select n_rows,clustered_index_size,sum_of_other_index_sizes from innodb_table_stats where database_name ='ytt' and table_name = 'ytt_sample_persist';
+--------+----------------------+--------------------------+
| n_rows | clustered_index_size | sum_of_other_index_sizes |
+--------+----------------------+--------------------------+
| 356960 | 15162 | 4113 |
+--------+----------------------+--------------------------+
1 row in set (0.00 sec)
其实表 ytt_sample_persist 真实数据为 40W 行。
mysql> select count(*) from ytt_sample_persist;
+----------+
| count(*) |
+----------+
| 406644 |
+----------+
1 row in set (0.90 sec)
强制更新统计信息,
mysql> analyze table ytt_sample_persist;
+------------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+------------------------+---------+----------+----------+
|
+------------------------+---------+----------+----------+
1 row in set (0.28 sec)
统计信息已经更新为最新,数据又离真实数据接近一点。
mysql> select n_rows,clustered_index_size,sum_of_other_index_sizes from innodb_table_stats where database_name ='ytt' and table_name ='ytt_sample_persist';
+--------+----------------------+--------------------------+
| n_rows | clustered_index_size | sum_of_other_index_sizes |
+--------+----------------------+--------------------------+
| 387202 | 16380 | 4562 |
+--------+----------------------+--------------------------+
1 row in set (0.01 sec)
索引统计信息保存在表 mysql.innodb_index_stats 里
比如表 ytt_sample_persist 索引统计信息
- Index_name:索引名字
- stat_name / stat_value:统计名字和对应的值
- sample_size:采样页个数
- stat_description:统计名字详细信息描述
比如表 ytt_sample_persist 的联合主键统计信息如下:
stat_name 显示的值 n_diff_pfx01 代表联合主键中第一个列状态名字,对应的 stat_value 为第一个列的唯一值个数;n_diff_pfx02 代表第二列状态名字,对应的 stat_value 为前两列的唯一值个数,以此类推。
n_leaf_pages /stat_value 代表叶子节点的页数目;size 代表索引的总页数。
mysql> select index_name,stat_name,stat_value,sample_size,stat_description from innodb_index_stats where database_name ='ytt' and table_name ='ytt_sample_persist' and index_name = 'PRIMARY';
+------------+--------------+------------+-------------+-----------------------------------+
| index_name | stat_name | stat_value | sample_size | stat_description |
+------------+--------------+------------+-------------+-----------------------------------+
| PRIMARY | n_diff_pfx01 | 14137 | 20 | i1 |
| PRIMARY | n_diff_pfx02 | 75398 | 20 | i1,i2 |
| PRIMARY | n_diff_pfx03 | 387202 | 20 | i1,i2,i3 |
| PRIMARY | n_leaf_pages | 15708 | NULL | Number of leaf pages in the index |
| PRIMARY | size | 16380 | NULL | Number of pages in the index |
+------------+--------------+------------+-------------+-----------------------------------+
5 rows in set (0.00 sec)
那关于表的统计信息相关知识点就介绍到此,了解这块对我们优化 SQL 来说,会更加得心应手。
总结
简单总结下,本篇主要介绍了 MySQL 表和索引的统计信息计算,包括持久化统计信息与非持久化统计信息。如果后期有 SQL 走的执行计划不对,或者不是最优的,那就可以断定相关统计信息太旧了,需要及时更新。比如有时候多表 JOIN 的顺序不对,导致查询效率变差,需要人工介入等等。
- JavaFX——(第一篇:介绍篇)
- 自编码器是什么?有什么用?这里有一份入门指南(附代码)
- UWP基础教程 - {x:DeferLoadStrategy}
- UWP基础教程 - 重启应用
- html5打开摄像头
- UWP基础教程 - App多语言支持
- Golang实现Fibonacii的几种算法
- 【译】使用 dotnet watch 开发 ASP.NET Core 应用
- vmware安装ubuntu12.04嵌套安装xen server(实现嵌套虚拟化)
- Golang语言切片slice的线程协程安全问题
- ASP.NET Core 在 Azure 开启 HTTPS
- 算法基础:最大递减数问题(Golang实现)
- 亲身经历的痛--database/sql: Stmt的使用以及坑
- Ubuntu上通过nginx部署Django笔记
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 放弃dagger?Anrdoi依赖注入框架koin
- WPF开发之C#中关闭进程的方式
- R语言实现:混合正态分布EM最大期望估计法
- MongoDB 指令
- 硬件设计之 Distributed Arithmetic 一例
- JavaScript 框架学习(JQuery)
- MongoDB 部署
- 结构体对齐原则在自定义协议解析时的妙用之法
- Spring JDBC 框架一个最简单的Hello World级别的例子
- Celery 分布式框架 学习
- .NET Core + K8S + Loki 玩转日志聚合
- varint是啥你真的知道么?
- 一篇文章带你入门移动安全
- Could not load JDBC driver class [com.mysql.jdbc.Driver]
- [Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库