一、为什么统计信息会不准

咱们先来聊聊MySQL这个老伙计。它就像个经验丰富的会计,每次做查询计划时都要翻翻账本(统计信息)来决定怎么干活最省力。但有时候这个会计也会犯糊涂,账本记错了,结果活就干岔了。

举个栗子,我上周就遇到个事儿。有张用户表users,原本就50万数据,后来业务爆发式增长到了500万。但MySQL的账本还记着"这表就50万条记录"。结果呢,本该走索引的查询,硬是变成了全表扫描,查询时间从0.1秒飙到了5秒。

-- 这是一个典型的统计信息不准导致的执行计划偏差案例
EXPLAIN SELECT * FROM users WHERE register_time > '2023-01-01';
-- 理想情况应该走register_time的索引,但因为统计信息不准
-- MySQL误判满足条件的数据量很大,选择了全表扫描

二、统计信息都记些啥

MySQL的统计信息主要记录这几样东西:

  1. 表的行数(这个最容易出问题)
  2. 索引的区分度(cardinality)
  3. 列值的分布情况
  4. 索引和数据页的分布情况

这些信息都存在mysql库的innodb_index_stats和innodb_table_stats表里。不过咱们平时不用直接查这些表,用SHOW INDEX FROM命令更方便。

-- 查看表的索引统计信息
SHOW INDEX FROM users;
-- 重点关注Cardinality列,这个值越接近表的总行数,索引区分度越高
-- 如果这个值很久没更新,就可能出问题

三、统计信息不准的常见症状

我总结了几种典型的"病症",遇到这些情况就该考虑是不是统计信息出问题了:

  1. 明明有合适的索引,查询却走全表扫描
  2. 执行计划突然变差,但SQL和数据结构都没改
  3. 表数据量变化很大(增删超过20%)
  4. 查询性能时好时坏,像抽风一样

上周我们生产环境就遇到个典型案例:

-- 症状:这个查询有时走索引,有时不走
SELECT * FROM orders WHERE user_id = 10086 AND status = 'completed';
-- 诊断:status字段的区分度统计信息不准
-- 实际completed状态订单很少,但统计信息显示很多
-- 导致MySQL误判要扫描大量数据,放弃使用user_id索引

四、怎么更新统计信息

既然知道问题在哪,解决办法就简单了。MySQL给我们准备了几种"药方":

  1. 自动更新:innodb_stats_auto_recalc默认是ON的,但只会在表数据变化超过10%时触发
  2. 手动更新:ANALYZE TABLE命令
  3. 彻底重建:有时候需要先删统计信息再重建
-- 最常用的手动更新方法
ANALYZE TABLE users;
-- 这个操作是Online DDL,不会锁表
-- 但大表可能耗时较长,建议在低峰期操作

-- 对于特别顽固的情况,可以尝试
SET GLOBAL innodb_stats_persistent=OFF;
ANALYZE TABLE users;
SET GLOBAL innodb_stats_persistent=ON;
-- 这样会强制重新采样统计信息

五、更高级的统计信息控制

对于特别重要的表,我们可以更精细地控制统计信息的收集方式:

  1. 调整采样页数:innodb_stats_persistent_sample_pages
  2. 使用持久化统计信息:innodb_stats_persistent=ON
  3. 针对特定表设置统计信息参数
-- 调整采样页数(默认是20)
SET GLOBAL innodb_stats_persistent_sample_pages=100;
-- 采样页数越多统计信息越准,但收集耗时越长

-- 为特定表设置不同的采样策略
ALTER TABLE users STATS_SAMPLE_PAGES=200;
-- 大表可以设大点,小表可以设小点

六、实战中的注意事项

在实际操作中,有几个坑得特别注意:

  1. 统计信息更新不是立即生效的,新会话才能看到新统计信息
  2. 主从架构中,统计信息不会自动同步
  3. 8.0版本后有了直方图统计信息,可以更精确但更耗资源
  4. 云数据库可能有特殊的统计信息策略

上周我们有个生产事故就是忽略了主从同步问题:

-- 主库上更新了统计信息
ANALYZE TABLE important_data;
-- 但从库没同步,导致主从执行计划不一致
-- 最终造成主库走索引很快,从库全表扫描超时
-- 解决方案是主从库都要手动执行ANALYZE

七、监控和维护建议

好的DBA应该建立统计信息的监控机制:

  1. 定期检查大表的统计信息时效性
  2. 在数据批量变更后主动更新统计信息
  3. 监控执行计划变化情况
  4. 重要查询可以固定执行计划

这里分享个我们用的监控脚本:

-- 检查超过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的导航地图,地图不准就容易走错路。通过今天的讨论,我们知道了:

  1. 统计信息不准会导致各种奇葩的执行计划问题
  2. MySQL提供了多种更新统计信息的方法
  3. 在生产环境中要特别注意主从一致性和时效性
  4. 建立监控机制可以防患于未然

记住,当查询突然变慢时,别急着加索引或改SQL,先看看统计信息是不是该更新了。就像老司机常说的:"先查统计信息,再谈优化!"