索引碎片与统计信息维护:执行计划突然变差的隐形杀手

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

你有没有遇到过这种情况:一条SQL昨天还跑得飞快,今天突然慢了一个数量级。执行计划没变、索引没删、数据量也没暴涨。你翻遍了慢查询日志、看了EXPLAIN、检查了服务器负载,一切看起来都正常------但就是慢了。

问题往往藏在两个容易被忽视的地方:索引碎片统计信息

索引碎片让数据库"翻书"翻得更慢;统计信息过时让优化器"看地图"看得更偏。两者叠加,一条正常的SQL就可能变成慢查询。

今天把这两个"隐形杀手"彻底拆开讲一遍。

一、索引碎片:数据在磁盘上"散架"了

索引碎片是什么?

想象一本精装书,原来页码连续、装订整齐。你不断在书里撕掉旧页、插入新页------书脊松动,页码错乱,翻书要找半天。索引碎片就是数据库里的"书脊松动"。

B+Tree索引是InnoDB的索引结构。在理想状态下,索引页是连续存储的,扫描时顺序读取,I/O效率很高。但随着频繁的增删改操作,原本连续的索引页开始出现空洞和不连续。这些空洞和不连续就是索引碎片

碎片的两种类型:

内部碎片:索引页内部有未被使用的空间。DELETE操作会留下"空洞",UPDATE可能导致页内空间分布不均。索引页的利用率降低,原来一个页能存100条记录,现在只能存70条,数据库就要读更多页。

外部碎片:索引页在物理存储上不再连续。INSERT导致页分裂,新的索引页被分配到磁盘的不同位置。范围扫描时,磁盘磁头需要在不同位置之间跳跃,I/O次数成倍增加。

碎片如何影响性能?

  • I/O次数增加:原本1次磁盘读能加载的索引数据,现在可能要读2-3次

  • 缓冲池命中率下降:碎片导致更多不连续的数据页被加载到内存,挤占热点数据

  • 范围扫描变慢:原本顺序扫描变成了跳跃式读取

如何诊断索引碎片?

MySQL没有直接的"碎片率"指标,但可以通过以下方式判断:

方法一:查看表空间碎片

bash 复制代码
SELECT TABLE_NAME, 
       DATA_LENGTH, 
       INDEX_LENGTH, 
       DATA_FREE,
       ROUND(DATA_FREE / (DATA_LENGTH + INDEX_LENGTH) * 100, 2) AS fragment_pct
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'your_db' 
  AND DATA_FREE > 0
ORDER BY DATA_FREE DESC;

DATA_FREE表示表中未使用的空间(碎片)。当DATA_FREE超过100MB时就应该关注了。

方法二:监控缓冲池命中率

Innodb_buffer_pool_read_requestsInnodb_buffer_pool_reads的比值低于1000:1时,表明可能存在碎片问题。

bash 复制代码
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';

方法三:对比索引大小与实际数据量

bash 复制代码
SHOW INDEX FROM your_table;

如果索引的Cardinality(不同值数量)与表实际行数明显不符,可能既存在碎片问题,也存在统计信息问题。

如何治理索引碎片?

方法一:OPTIMIZE TABLE

bash 复制代码
OPTIMIZE TABLE your_table;

OPTIMIZE TABLE对InnoDB表实际上是重建表,回收空间、消除碎片、更新统计信息。但会加读锁,大表操作时可能阻塞业务。

适用场景:业务低峰期、中小表、碎片率较高时。

方法二:ALTER TABLE重建

bash 复制代码
ALTER TABLE your_table ENGINE=InnoDB;

效果与OPTIMIZE TABLE类似,同样会锁表。

方法三:pt-online-schema-change(推荐线上使用)

生产环境推荐使用Percona Toolkit的pt-online-schema-change工具,可以在线重建表,不阻塞读写。

bash

bash 复制代码
pt-online-schema-change --alter "ENGINE=InnoDB" D=your_db,t=your_table

什么时候该治理碎片?

碎片率 处理建议 说明
<5% 暂不处理 正常范围
5%-20% 观察,可择机处理 微软建议5%-30%重组索引
>20% 尽快安排处理 性能影响明显
DATA_FREE>100MB 优先处理 空间浪费严重

二、统计信息:优化器的"眼睛"花了

统计信息是什么?

统计信息是优化器做出执行计划决策的"眼睛"------它告诉优化器表有多大、列有多少个不同值、数据分布如何。优化器根据这些信息计算每种执行方式的"代价",然后选择代价最小的方案。

统计信息包含什么?

  • 表的总行数

  • 每列的不同值数量(Cardinality)

  • 列的NULL值比例

  • 数据分布(直方图)

统计信息过时的后果:优化器"看错"了

如果统计信息过旧,优化器就会基于错误的信息做决策:

  • 表实际有1000万行,统计信息显示只有100万行 → 优化器可能选择全表扫描,认为"反正没多少数据"

  • 某列实际有50万个不同值,统计信息显示只有5000个 → 优化器低估了索引的选择性,放弃使用该索引

典型案例:一张日志表有500万行,user_id列上有索引。业务方反馈一条查询突然变慢:

bash 复制代码
SELECT * FROM user_logs WHERE user_id = 12345 AND create_time > '2026-01-01';

正常情况下走(user_id, create_time)复合索引,扫描几十行就返回。但实际执行计划显示type=ALL全表扫描。

检查统计信息后发现,这张表的统计信息还是三个月前收集的------当时表只有50万行。优化器根据旧统计信息估算:user_id=12345可能返回很多行(旧数据中该用户有大量记录),全表扫描更"划算"。执行ANALYZE TABLE更新统计信息后,查询从5秒降到0.05秒。

如何检查统计信息是否过时?

方法一:对比EXPLAIN的rows与实际行数

执行EXPLAIN后看rows列的估算值,再实际执行查询对比实际扫描行数。如果估算值和实际值差了一个数量级以上,统计信息很可能过旧。

方法二:查看统计信息的最后更新时间

bash 复制代码
SELECT TABLE_NAME, UPDATE_TIME 
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'your_db';

如果统计信息已经几周甚至几个月没有更新,而表的数据变化很大,就需要执行ANALYZE TABLE

方法三:查看索引的Cardinality

bash 复制代码
SHOW INDEX FROM your_table;

Cardinality列显示索引列的不同值数量估算。如果Cardinality与实际明显不符,说明统计信息需要更新。

如何更新统计信息?

bash 复制代码
ANALYZE TABLE your_table;

ANALYZE TABLE会重新收集表的统计信息(行数、Cardinality、数据分布等),让优化器获得准确的数据分布。

对于MySQL 8.0,还可以创建直方图来改善数据分布不均时的估算:

bash 复制代码
ANALYZE TABLE your_table UPDATE HISTOGRAM ON column_name WITH 100 BUCKETS;

统计信息维护的最佳实践:

场景 建议操作 说明
批量数据导入后 立即执行ANALYZE TABLE 数据变化巨大
大量数据删除后 立即执行ANALYZE TABLE 表行数变化显著
表结构变更后 执行ANALYZE TABLE 新索引需要统计信息
日常定期维护 每周或每月一次 根据数据变化频率调整
EXPLAINrows与实际差距>10倍 立即执行ANALYZE TABLE 优化器选错索引的风险高

三、碎片与统计信息的联动维护

索引碎片和统计信息常常"结伴作案"------表经过了大量增删改,既产生了碎片,又让统计信息过时。一条SQL突然变慢,往往是两者的叠加效应。

联动维护策略:

1. 定期巡检清单

建议每周或每月执行一次以下检查:

bash 复制代码
-- 1. 检查碎片情况
SELECT TABLE_NAME, DATA_LENGTH, INDEX_LENGTH, DATA_FREE,
       ROUND(DATA_FREE / (DATA_LENGTH + INDEX_LENGTH) * 100, 2) AS fragment_pct
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'your_db' 
  AND DATA_FREE > 0
ORDER BY DATA_FREE DESC;

-- 2. 检查统计信息状态
SHOW INDEX FROM your_table;

2. 日常维护窗口

在业务低峰期(如凌晨),对高频变动的表依次执行:

bash 复制代码
-- 先更新统计信息
ANALYZE TABLE your_table;
-- 碎片严重时再重建
OPTIMIZE TABLE your_table;

3. 生产环境安全操作规范

  • 第一次操作先在测试表上演练

  • 线上环境先用pt-online-schema-change或从库测试

  • 监控操作期间的锁等待和业务影响

  • 准备回滚方案

四、总结

执行计划突然变差的"隐形杀手",往往不是SQL写错了,而是索引碎片和统计信息这两个"看得到但容易被忽略"的问题。

  • 索引碎片让数据库"翻书"翻得更慢------增加I/O、降低缓冲池命中率

  • 统计信息过时让优化器"看地图"看得更偏------选错索引、走错执行计划

把索引碎片治理和统计信息维护纳入日常运维体系,你就能在业务方投诉之前,提前发现并消除隐患。定期巡检、及时治理、安全操作------这三件事做好了,执行计划"突然变差"的情况会越来越少。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
Discipline~Hai16 小时前
ARM01-ARM体系架构
linux·c语言·arm开发·架构
XUHUOJUN16 小时前
AKS 不是安装在 Windows Server 上,而是运行在 Windows Server 之上的 Azure 平台能力
windows·架构·k8s·azure local·azure stack
爱勇宝17 小时前
AI不会淘汰所有人,但会淘汰这6种人
前端·后端·程序员
Cosolar17 小时前
AI Agent 架构原理详解:从一次提问到任务完成的完整闭环
人工智能·后端·架构
会周易的程序员18 小时前
Libnodave S7 通信库:架构设计与实现解析
linux·c++·物联网·架构·c·s7·工业协议
小小猪的春天18 小时前
团队级 Code Review Skill 实践:4个人、4个AI、1套规则,CR从找bug变成做决策
后端·架构
Mico1818 小时前
MySQL 8.0.35 GTID 主从复制搭建-基于GITD
android·mysql·adb
摸鱼师moko18 小时前
AI 来了之后,我的心流状态去哪了
人工智能·程序员
码农学院19 小时前
建筑机械行业AI搜索优化技术方案:基于Spring Cloud微服务架构的文件管理与智能化内容推荐系统
人工智能·spring cloud·架构