数据库定期自动碎片优化提速脚本编写与实战

为什么要给数据库做定期碎片优化

MySQL 数据库运行一段时间后,频繁的增删改操作会让表和索引内部产生大量碎片(不连续的数据页)。
这些碎片会增加磁盘 I/O,拖慢查询和写入速度,尤其在 InnoDB 引擎上表现明显。
手工执行 OPTIMIZE TABLE 能重建表并回收空间,但靠人脑记住每周做一次不现实。
本文会带你编写一个小脚本,配合系统定时任务,让数据库在业务低峰期自动完成碎片整理,省心又安全。

你需要准备什么

  • 一台装有 MySQL 或 MariaDB 的服务器(可使用宝塔面板或命令行管理)
  • 有权限操作 OPTIMIZE TABLE 的数据库账号(建议用 root 或单独的管理账号)
  • 基本的命令行操作能力(会登录服务器、编辑文件)
  • 如果使用宝塔面板,可以直接用它的计划任务功能代替 crontab

编写自定义碎片优化脚本

我们写一个 Shell 脚本,逻辑如下:

  1. 通过 mysql 命令连接数据库。
  2. information_schema.TABLES 中选出碎片率(Data_free / Data_length)超过一定比例的表。
  3. 对符合条件的表依次执行 OPTIMIZE TABLE 并记录结果到日志。
  4. 如果某张表碎片率很低则跳过,避免频繁重建锁定。
#!/bin/bash
# 数据库连接参数(请根据实际修改)
DB_USER="root"
DB_PASS="your_password"
DB_HOST="localhost"
DB_NAME="your_database"
LOG_FILE="/var/log/optimize_$(date +%Y%m%d_%H%M%S).log"

# 碎片率阈值(百分比),建议 25 以上才优化
THRESHOLD=25

# 查询需要优化的表
SQL="SELECT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS t
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = '$DB_NAME'
AND DATA_LENGTH > 0
AND (DATA_FREE / DATA_LENGTH) * 100 > $THRESHOLD
ORDER BY (DATA_FREE / DATA_LENGTH) DESC;"

# 执行查询并循环处理
mysql -u$DB_USER -p$DB_PASS -h$DB_HOST -e "$SQL" -N | while read table_name; do
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting optimize $table_name" >> $LOG_FILE
    mysql -u$DB_USER -p$DB_PASS -h$DB_HOST -e "OPTIMIZE TABLE $table_name;" >> $LOG_FILE 2>&1
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Finished optimize $table_name" >> $LOG_FILE
done

echo "[$(date '+%Y-%m-%d %H:%M:%S')] All tasks completed." >> $LOG_FILE
注意:脚本中密码明文写在脚本里有一定风险,生产环境建议使用 .my.cnf 配置文件或加密工具。示例中的 -p$DB_PASS 可替换为 --defaults-file=/etc/my.cnf 这种更安全的方式。

部署自动定时任务

方式一:crontab(纯命令行环境)

  1. 将上面的脚本保存为 /usr/local/bin/optimize_db.sh,并给予执行权限:
   chmod +x /usr/local/bin/optimize_db.sh
  1. 编辑 crontab:
   crontab -e
  1. 添加一行(每周日凌晨 3 点执行):
   0 3 * * 0 /usr/local/bin/optimize_db.sh
  1. 保存退出。

方式二:宝塔面板计划任务

  1. 登录宝塔面板,点击左侧“计划任务”。
  2. 添加任务 – 任务类型选“Shell脚本”。
  3. 任务名称填“数据库碎片优化”,执行周期选“每周”,具体时间建议凌晨 2-4 点之间。
  4. 脚本内容粘贴上面的完整脚本(注意修改数据库账号密码)。
  5. 点击“执行”测试一次,确认日志目录能正常写入。

避坑指南(一定先看再执行)

  • 不要在业务高峰期运行OPTIMIZE TABLE 会锁表(InnoDB 为元数据锁),期间该表的所有读写都会阻塞。建议选在凌晨低峰期。
  • 确保磁盘空间充足:优化过程需要临时空间(约原表大小 1.1 倍),空间不足会报错 Table rebuild failed
  • 大表可能会很久:几十 GB 的表优化可能需要几十分钟甚至几小时,crontab 里要预留足够时间窗口,或者只针对碎片率高的小表。
  • 日志膨胀处理:每次执行都生成一个新日志文件,建议定期清理,比如 crontab 里再加一条 find /var/log/ -name 'optimize_*.log' -mtime +30 -delete

如何验证优化效果

  1. 查看日志:执行 cat /var/log/optimize_*.log,看是否有“OK”状态。
  2. 检查碎片率是否下降:登录 MySQL 执行以下 SQL:
   SELECT TABLE_NAME, ROUND((DATA_FREE/DATA_LENGTH)*100,2) AS fragmentation
   FROM information_schema.TABLES
   WHERE TABLE_SCHEMA='your_database';
  1. 对比查询速度:优化前用 EXPLAINSELECT COUNT(*) 记录耗时,优化后跑同样的 SQL,通常会发现 I/O 等待时间下降。
  2. 检查表大小是否缩小:若碎片率原本很高,优化后 DATA_LENGTH 一般会减少,说明空间已回收。

高频问题解答

Q:脚本一直报错“Access denied for user”,怎么办?
A:检查 MySQL 账号是否有 PROCESSSELECTOPTIMIZE 权限。可以用 root 先授权:GRANT PROCESS, SELECT, INSERT ON *.* TO 'your_user'@'localhost'; 然后刷新权限。

Q:一张表很大,碎片率只有 10%,要不要优化?
A:不建议。InnoDB 引擎的碎片整理很重,阈值设为 25%-30% 比较合理,避免频繁重建影响性能。

Q:宝塔计划任务执行后没有日志生成?
A:检查日志目录是否存在(例如 /var/log/),以及脚本中写的路径是否有写权限。可以把日志路径临时改为 /tmp/ 测试。

Q:脚本里所有表都处理,但我只想优化某个数据库下的几张核心表,怎么改?
A:把 SQL 中的 TABLE_SCHEMA = '$DB_NAME' 改成具体的库名,再加一个条件 AND TABLE_NAME IN ('table1','table2') 即可。

分享到:
上一篇
大模型推理服务器散热降噪改造方案:从硬件选型到操作全流程
下一篇
AIOps智能故障自愈服务器自动处理配置教程
1
系统公告

机房迁移升级通知

尊敬的用户: IP 段 103.23.148.x、156.224.29.x 原香港一区线路波动、攻击频繁,平台定于 7 月 5 日凌晨分批迁移至香港 GIA 机房,硬件升级 AMD 铂金机型。 迁移均在凌晨操作,最大程度降低业务影响,迁移期间服务器临时关机; 升级后配置不降低、费用不涨价,数据默认同步迁移; 迁移后 IP 全部更换,请及时修改域名解析、防火墙白名单; 建议提前备份重要数据,有问题可联系在线客服。 感谢理解与支持! 泽御云科技 2026.06.30
服务中心
客服
在线客服
24小时为您服务
咨询
联系我们
联系我们,为您的业务提供专属服务。
24/7 技术支持
如果您遇到寻求进一步的帮助,请过工单与我们进行联系。
24/7 即时支持
泽御云
售前客服
泽御云
泽御云
售后客服
泽御云
技术支持
评价
您对当前页面的整体感受是否满意?
😞
非常不满意
😕
不满意
😐
一般
🙂
满意
😊
非常满意