一、为什么统计信息会不准
咱们先来聊聊MySQL这个老伙计。它就像个经验丰富的会计,每次做查询计划时都要翻翻账本(统计信息)来决定怎么干活最省力。但有时候这个会计也会犯糊涂,账本记错了,结果活就干岔了。
举个栗子,我上周就遇到个事儿。有张用户表users,原本就50万数据,后来业务爆发式增长到了500万。但MySQL的账本还记着"这表就50万条记录"。结果呢,本该走索引的查询,硬是变成了全表扫描,查询时间从0.1秒飙到了5秒。
-- 这是一个典型的统计信息不准导致的执行计划偏差案例
EXPLAIN SELECT * FROM users WHERE register_time > '2023-01-01';
-- 理想情况应该走register_time的索引,但因为统计信息不准
-- MySQL误判满足条件的数据量很大,选择了全表扫描
二、统计信息都记些啥
MySQL的统计信息主要记录这几样东西:
- 表的行数(这个最容易出问题)
- 索引的区分度(cardinality)
- 列值的分布情况
- 索引和数据页的分布情况
这些信息都存在mysql库的innodb_index_stats和innodb_table_stats表里。不过咱们平时不用直接查这些表,用SHOW INDEX FROM命令更方便。
-- 查看表的索引统计信息
SHOW INDEX FROM users;
-- 重点关注Cardinality列,这个值越接近表的总行数,索引区分度越高
-- 如果这个值很久没更新,就可能出问题
三、统计信息不准的常见症状
我总结了几种典型的"病症",遇到这些情况就该考虑是不是统计信息出问题了:
- 明明有合适的索引,查询却走全表扫描
- 执行计划突然变差,但SQL和数据结构都没改
- 表数据量变化很大(增删超过20%)
- 查询性能时好时坏,像抽风一样
上周我们生产环境就遇到个典型案例:
-- 症状:这个查询有时走索引,有时不走
SELECT * FROM orders WHERE user_id = 10086 AND status = 'completed';
-- 诊断:status字段的区分度统计信息不准
-- 实际completed状态订单很少,但统计信息显示很多
-- 导致MySQL误判要扫描大量数据,放弃使用user_id索引
四、怎么更新统计信息
既然知道问题在哪,解决办法就简单了。MySQL给我们准备了几种"药方":
- 自动更新:innodb_stats_auto_recalc默认是ON的,但只会在表数据变化超过10%时触发
- 手动更新:ANALYZE TABLE命令
- 彻底重建:有时候需要先删统计信息再重建
-- 最常用的手动更新方法
ANALYZE TABLE users;
-- 这个操作是Online DDL,不会锁表
-- 但大表可能耗时较长,建议在低峰期操作
-- 对于特别顽固的情况,可以尝试
SET GLOBAL innodb_stats_persistent=OFF;
ANALYZE TABLE users;
SET GLOBAL innodb_stats_persistent=ON;
-- 这样会强制重新采样统计信息
五、更高级的统计信息控制
对于特别重要的表,我们可以更精细地控制统计信息的收集方式:
- 调整采样页数:innodb_stats_persistent_sample_pages
- 使用持久化统计信息:innodb_stats_persistent=ON
- 针对特定表设置统计信息参数
-- 调整采样页数(默认是20)
SET GLOBAL innodb_stats_persistent_sample_pages=100;
-- 采样页数越多统计信息越准,但收集耗时越长
-- 为特定表设置不同的采样策略
ALTER TABLE users STATS_SAMPLE_PAGES=200;
-- 大表可以设大点,小表可以设小点
六、实战中的注意事项
在实际操作中,有几个坑得特别注意:
- 统计信息更新不是立即生效的,新会话才能看到新统计信息
- 主从架构中,统计信息不会自动同步
- 8.0版本后有了直方图统计信息,可以更精确但更耗资源
- 云数据库可能有特殊的统计信息策略
上周我们有个生产事故就是忽略了主从同步问题:
-- 主库上更新了统计信息
ANALYZE TABLE important_data;
-- 但从库没同步,导致主从执行计划不一致
-- 最终造成主库走索引很快,从库全表扫描超时
-- 解决方案是主从库都要手动执行ANALYZE
七、监控和维护建议
好的DBA应该建立统计信息的监控机制:
- 定期检查大表的统计信息时效性
- 在数据批量变更后主动更新统计信息
- 监控执行计划变化情况
- 重要查询可以固定执行计划
这里分享个我们用的监控脚本:
-- 检查超过7天未更新的统计信息
SELECT
table_schema,
table_name,
update_time
FROM
information_schema.tables
WHERE
update_time < DATE_SUB(NOW(), INTERVAL 7 DAY)
AND table_schema NOT IN ('mysql','information_schema','performance_schema')
ORDER BY
update_time ASC;
八、总结
统计信息就像MySQL的导航地图,地图不准就容易走错路。通过今天的讨论,我们知道了:
- 统计信息不准会导致各种奇葩的执行计划问题
- MySQL提供了多种更新统计信息的方法
- 在生产环境中要特别注意主从一致性和时效性
- 建立监控机制可以防患于未然
记住,当查询突然变慢时,别急着加索引或改SQL,先看看统计信息是不是该更新了。就像老司机常说的:"先查统计信息,再谈优化!"
评论