MySQL中存储过程大表分批删除历史数据

MySQL中大表分批删除历史数据

TL;DR: 千万级大表删历史数据,绝不能一条 DELETE 搞定。本文对比 4 种方案,给出分批删除的完整操作步骤、Mermaid 流程图、可运行 SQL 示例和踩坑清单。

### 文章目录

  • [MySQL中大表分批删除历史数据](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [@toc](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [一、为什么不能一条 DELETE 搞定](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [二、操作前安全检查清单](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [2.1 确认待删数据量](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [2.2 确认索引](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [2.3 确认备份](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [2.4 确认执行窗口](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [三、四种方案对比](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [选型决策树](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [四、方案一:存储过程分批删除(推荐快速上手)](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [4.1 完整存储过程](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [4.2 调用示例](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [4.3 执行流程图](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [4.4 关键参数调优](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [4.5 Navicat 中 DELIMITER 报错的替代方案](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [五、方案二:pt-archiver(生产环境首选)](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [5.1 安装](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [5.2 基本用法:直接删除](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [5.3 进阶用法:归档到另一张表](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [5.4 关键参数说明](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [5.5 pt-archiver vs 存储过程](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [六、方案三:应用层分批删除](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [6.1 Python 示例](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [6.2 执行效果](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [6.3 与定时任务集成](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [七、方案四:分区表 DROP PARTITION(最快最干净)](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [7.1 前提:表已按时间分区](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [7.2 删除操作:一条命令搞定](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [7.3 分区方案 vs 分批删除](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [八、删除后:回收磁盘空间](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [8.1 方法一:OPTIMIZE TABLE](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [8.2 方法二:ALTER TABLE 重建](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [8.3 方法三:pt-online-schema-change(生产推荐)](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [8.4 验证空间回收](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [九、常见问题与排障](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [十、总结与选型建议](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [决策总结](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [关键原则](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [附录](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [相关工具与文档](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)
  • [术语表](#文章目录 MySQL中大表分批删除历史数据 @[toc] 一、为什么不能一条 DELETE 搞定 二、操作前安全检查清单 2.1 确认待删数据量 2.2 确认索引 2.3 确认备份 2.4 确认执行窗口 三、四种方案对比 选型决策树 四、方案一:存储过程分批删除(推荐快速上手) 4.1 完整存储过程 4.2 调用示例 4.3 执行流程图 4.4 关键参数调优 4.5 Navicat 中 DELIMITER 报错的替代方案 五、方案二:pt-archiver(生产环境首选) 5.1 安装 5.2 基本用法:直接删除 5.3 进阶用法:归档到另一张表 5.4 关键参数说明 5.5 pt-archiver vs 存储过程 六、方案三:应用层分批删除 6.1 Python 示例 6.2 执行效果 6.3 与定时任务集成 七、方案四:分区表 DROP PARTITION(最快最干净) 7.1 前提:表已按时间分区 7.2 删除操作:一条命令搞定 7.3 分区方案 vs 分批删除 八、删除后:回收磁盘空间 8.1 方法一:OPTIMIZE TABLE 8.2 方法二:ALTER TABLE 重建 8.3 方法三:pt-online-schema-change(生产推荐) 8.4 验证空间回收 九、常见问题与排障 十、总结与选型建议 决策总结 关键原则 附录 相关工具与文档 术语表)

一、为什么不能一条 DELETE 搞定

一张 5000 万行的表,直接执行 DELETE FROM orders WHERE created_at < '2024-01-01' 会删除 3000 万行。这条语句在 InnoDB 引擎下的行为是:
MySQL 从库 MySQL 主库 应用服务 MySQL 从库 MySQL 主库 应用服务 #mermaid-svg-j0wt6hMrpkuJtFpP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j0wt6hMrpkuJtFpP .error-icon{fill:#552222;}#mermaid-svg-j0wt6hMrpkuJtFpP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j0wt6hMrpkuJtFpP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j0wt6hMrpkuJtFpP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j0wt6hMrpkuJtFpP .marker.cross{stroke:#333333;}#mermaid-svg-j0wt6hMrpkuJtFpP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j0wt6hMrpkuJtFpP p{margin:0;}#mermaid-svg-j0wt6hMrpkuJtFpP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j0wt6hMrpkuJtFpP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j0wt6hMrpkuJtFpP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-j0wt6hMrpkuJtFpP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-j0wt6hMrpkuJtFpP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-j0wt6hMrpkuJtFpP .sequenceNumber{fill:white;}#mermaid-svg-j0wt6hMrpkuJtFpP #sequencenumber{fill:#333;}#mermaid-svg-j0wt6hMrpkuJtFpP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-j0wt6hMrpkuJtFpP .messageText{fill:#333;stroke:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j0wt6hMrpkuJtFpP .labelText,#mermaid-svg-j0wt6hMrpkuJtFpP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .loopText,#mermaid-svg-j0wt6hMrpkuJtFpP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j0wt6hMrpkuJtFpP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-j0wt6hMrpkuJtFpP .noteText,#mermaid-svg-j0wt6hMrpkuJtFpP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-j0wt6hMrpkuJtFpP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j0wt6hMrpkuJtFpP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j0wt6hMrpkuJtFpP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j0wt6hMrpkuJtFpP .actorPopupMenu{position:absolute;}#mermaid-svg-j0wt6hMrpkuJtFpP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-j0wt6hMrpkuJtFpP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j0wt6hMrpkuJtFpP .actor-man circle,#mermaid-svg-j0wt6hMrpkuJtFpP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-j0wt6hMrpkuJtFpP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 锁定 3000 万行 生成 3000 万条 undo log 写入巨量 binlog 连接超时 / 查询被 kill 从库追不上主库 主从延迟飙升至分钟级 DELETE ... WHERE created_at < '2024-01-01' 执行中...(持续数小时) binlog 洪涌入

具体问题汇总:

问题 原理 影响
长时间锁表 大批量 DELETE 持有行锁/间隙锁 其他读写请求被阻塞,业务不可用
主从延迟 巨量 binlog 集中写入 relay log 从库落后主库数分钟甚至数小时
事务膨胀 undo log + redo log 暴增 磁盘空间打满,甚至触发 OOM
超时中断 客户端/代理层有查询超时限制 大事务被中途打断,数据处于中间态
影响优化器 统计信息剧烈波动 查询计划可能变差,影响线上查询

正确的做法是分批删除:每次只删一小批(如 5000~50000 条),每批之间暂停一段时间,让数据库有时间处理其他请求和同步从库。


二、操作前安全检查清单

在执行任何删除操作之前,逐项确认以下条件。跳过任何一项都可能导致生产事故。
#mermaid-svg-BKeIB7ixG50HygBR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BKeIB7ixG50HygBR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BKeIB7ixG50HygBR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BKeIB7ixG50HygBR .error-icon{fill:#552222;}#mermaid-svg-BKeIB7ixG50HygBR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BKeIB7ixG50HygBR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BKeIB7ixG50HygBR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BKeIB7ixG50HygBR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BKeIB7ixG50HygBR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BKeIB7ixG50HygBR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BKeIB7ixG50HygBR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BKeIB7ixG50HygBR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BKeIB7ixG50HygBR .marker.cross{stroke:#333333;}#mermaid-svg-BKeIB7ixG50HygBR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BKeIB7ixG50HygBR p{margin:0;}#mermaid-svg-BKeIB7ixG50HygBR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BKeIB7ixG50HygBR .cluster-label text{fill:#333;}#mermaid-svg-BKeIB7ixG50HygBR .cluster-label span{color:#333;}#mermaid-svg-BKeIB7ixG50HygBR .cluster-label span p{background-color:transparent;}#mermaid-svg-BKeIB7ixG50HygBR .label text,#mermaid-svg-BKeIB7ixG50HygBR span{fill:#333;color:#333;}#mermaid-svg-BKeIB7ixG50HygBR .node rect,#mermaid-svg-BKeIB7ixG50HygBR .node circle,#mermaid-svg-BKeIB7ixG50HygBR .node ellipse,#mermaid-svg-BKeIB7ixG50HygBR .node polygon,#mermaid-svg-BKeIB7ixG50HygBR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BKeIB7ixG50HygBR .rough-node .label text,#mermaid-svg-BKeIB7ixG50HygBR .node .label text,#mermaid-svg-BKeIB7ixG50HygBR .image-shape .label,#mermaid-svg-BKeIB7ixG50HygBR .icon-shape .label{text-anchor:middle;}#mermaid-svg-BKeIB7ixG50HygBR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BKeIB7ixG50HygBR .rough-node .label,#mermaid-svg-BKeIB7ixG50HygBR .node .label,#mermaid-svg-BKeIB7ixG50HygBR .image-shape .label,#mermaid-svg-BKeIB7ixG50HygBR .icon-shape .label{text-align:center;}#mermaid-svg-BKeIB7ixG50HygBR .node.clickable{cursor:pointer;}#mermaid-svg-BKeIB7ixG50HygBR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BKeIB7ixG50HygBR .arrowheadPath{fill:#333333;}#mermaid-svg-BKeIB7ixG50HygBR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BKeIB7ixG50HygBR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BKeIB7ixG50HygBR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BKeIB7ixG50HygBR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BKeIB7ixG50HygBR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BKeIB7ixG50HygBR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BKeIB7ixG50HygBR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BKeIB7ixG50HygBR .cluster text{fill:#333;}#mermaid-svg-BKeIB7ixG50HygBR .cluster span{color:#333;}#mermaid-svg-BKeIB7ixG50HygBR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BKeIB7ixG50HygBR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BKeIB7ixG50HygBR rect.text{fill:none;stroke-width:0;}#mermaid-svg-BKeIB7ixG50HygBR .icon-shape,#mermaid-svg-BKeIB7ixG50HygBR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BKeIB7ixG50HygBR .icon-shape p,#mermaid-svg-BKeIB7ixG50HygBR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BKeIB7ixG50HygBR .icon-shape .label rect,#mermaid-svg-BKeIB7ixG50HygBR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BKeIB7ixG50HygBR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BKeIB7ixG50HygBR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BKeIB7ixG50HygBR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 没有

没有
确认了
没有

没有
确认了
开始检查
有备份吗?
先做备份
确认了影响行数?
先 SELECT COUNT
删除字段有索引?
先加索引
确认了执行窗口?
选择低峰期
可以执行

2.1 确认待删数据量

sql 复制代码
-- 查看有多少数据需要删除
SELECT COUNT(*) AS total_to_delete
FROM orders
WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);

记录这个数字,后续用来验证删除进度。如果总量超过表总行数的 50% ,考虑使用 CREATE TABLE ... SELECT 保留新数据 + RENAME TABLE 的方案(详见方案四)。

2.2 确认索引

分批删除的 WHERE 条件必须走索引,否则每批都会全表扫描:

sql 复制代码
-- 检查 created_at 上是否有索引
SHOW INDEX FROM orders WHERE Column_name = 'created_at';

如果没有索引,需要先添加。大表加索引建议在低峰期使用 pt-online-schema-change

bash 复制代码
pt-online-schema-change \
  --alter "ADD INDEX idx_created_at (created_at)" \
  D=your_db,t=orders \
  --execute

2.3 确认备份

sql 复制代码
-- 至少确认最近的备份时间点
SHOW VARIABLES LIKE 'log_bin%';
-- 确认 binlog 是否开启(用于 point-in-time recovery)
SHOW VARIABLES LIKE 'log_bin';

2.4 确认执行窗口

检查项 说明
当前 TPS/QPS 在低峰期执行,SHOW GLOBAL STATUS LIKE 'Queries'
主从延迟 SHOW SLAVE STATUS 确认 Seconds_Behind_Master = 0
磁盘空间 df -h 确认 undo/redo log 所在分区有足够余量
业务通知 提前通知相关团队,预留应急回滚窗口

三、四种方案对比

不同场景适合不同方案,下表给出完整对比:

维度 存储过程 pt-archiver 应用层分批 分区表 DROP
适用场景 一次性清理 定期归档清理 业务逻辑内 按时间分区
需要装工具 是(Percona Toolkit) 否(需提前设计)
对主从影响 可控(调 SLEEP) 最小(内置保护) 可控 几乎无影响
空间回收 需额外 OPTIMIZE 自动(DELETE 模式) 需额外处理 自动释放
操作复杂度 低(需提前建分区)
可中断恢复 重新 CALL 即可 天然支持(幂等) 天然支持 不支持(DROP 不可逆)
推荐度 4/5 5/5 3/5 5/5(有分区时)

选型决策树

#mermaid-svg-7fOorhYsOhmW4dud{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7fOorhYsOhmW4dud .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7fOorhYsOhmW4dud .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7fOorhYsOhmW4dud .error-icon{fill:#552222;}#mermaid-svg-7fOorhYsOhmW4dud .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7fOorhYsOhmW4dud .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7fOorhYsOhmW4dud .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7fOorhYsOhmW4dud .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7fOorhYsOhmW4dud .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7fOorhYsOhmW4dud .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7fOorhYsOhmW4dud .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7fOorhYsOhmW4dud .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7fOorhYsOhmW4dud .marker.cross{stroke:#333333;}#mermaid-svg-7fOorhYsOhmW4dud svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7fOorhYsOhmW4dud p{margin:0;}#mermaid-svg-7fOorhYsOhmW4dud .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7fOorhYsOhmW4dud .cluster-label text{fill:#333;}#mermaid-svg-7fOorhYsOhmW4dud .cluster-label span{color:#333;}#mermaid-svg-7fOorhYsOhmW4dud .cluster-label span p{background-color:transparent;}#mermaid-svg-7fOorhYsOhmW4dud .label text,#mermaid-svg-7fOorhYsOhmW4dud span{fill:#333;color:#333;}#mermaid-svg-7fOorhYsOhmW4dud .node rect,#mermaid-svg-7fOorhYsOhmW4dud .node circle,#mermaid-svg-7fOorhYsOhmW4dud .node ellipse,#mermaid-svg-7fOorhYsOhmW4dud .node polygon,#mermaid-svg-7fOorhYsOhmW4dud .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7fOorhYsOhmW4dud .rough-node .label text,#mermaid-svg-7fOorhYsOhmW4dud .node .label text,#mermaid-svg-7fOorhYsOhmW4dud .image-shape .label,#mermaid-svg-7fOorhYsOhmW4dud .icon-shape .label{text-anchor:middle;}#mermaid-svg-7fOorhYsOhmW4dud .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7fOorhYsOhmW4dud .rough-node .label,#mermaid-svg-7fOorhYsOhmW4dud .node .label,#mermaid-svg-7fOorhYsOhmW4dud .image-shape .label,#mermaid-svg-7fOorhYsOhmW4dud .icon-shape .label{text-align:center;}#mermaid-svg-7fOorhYsOhmW4dud .node.clickable{cursor:pointer;}#mermaid-svg-7fOorhYsOhmW4dud .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7fOorhYsOhmW4dud .arrowheadPath{fill:#333333;}#mermaid-svg-7fOorhYsOhmW4dud .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7fOorhYsOhmW4dud .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7fOorhYsOhmW4dud .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7fOorhYsOhmW4dud .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7fOorhYsOhmW4dud .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7fOorhYsOhmW4dud .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7fOorhYsOhmW4dud .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7fOorhYsOhmW4dud .cluster text{fill:#333;}#mermaid-svg-7fOorhYsOhmW4dud .cluster span{color:#333;}#mermaid-svg-7fOorhYsOhmW4dud div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7fOorhYsOhmW4dud .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7fOorhYsOhmW4dud rect.text{fill:none;stroke-width:0;}#mermaid-svg-7fOorhYsOhmW4dud .icon-shape,#mermaid-svg-7fOorhYsOhmW4dud .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7fOorhYsOhmW4dud .icon-shape p,#mermaid-svg-7fOorhYsOhmW4dud .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7fOorhYsOhmW4dud .icon-shape .label rect,#mermaid-svg-7fOorhYsOhmW4dud .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7fOorhYsOhmW4dud .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7fOorhYsOhmW4dud .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7fOorhYsOhmW4dud :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
没有

没有

不能
要删除历史数据
表有按时间分区吗?
方案四: DROP PARTITION (最快最干净)
装了 Percona Toolkit?
方案二: pt-archiver (生产首选)
能改应用代码吗?
方案三: 应用层分批 (长期方案)
方案一: 存储过程 (最快上手)


四、方案一:存储过程分批删除(推荐快速上手)

这是最简单直接的方案,不需要安装任何额外工具,在 Navicat 或任何 MySQL 客户端中即可执行。

4.1 完整存储过程

sql 复制代码
DELIMITER $$

DROP PROCEDURE IF EXISTS batch_delete_old_data$$

CREATE PROCEDURE batch_delete_old_data(
    IN p_table     VARCHAR(64),   -- 表名
    IN p_condition VARCHAR(255),  -- 删除条件
    IN p_batch     INT,           -- 每批删除条数
    IN p_sleep     DECIMAL(3,1)   -- 批间暂停秒数
)
BEGIN
    DECLARE v_rows   INT DEFAULT 1;
    DECLARE v_total  BIGINT DEFAULT 0;
    DECLARE v_sql    TEXT;

    -- 动态拼接 SQL,支持任意表和条件
    SET v_sql = CONCAT('DELETE FROM ', p_table,
                       ' WHERE ', p_condition,
                       ' LIMIT ', p_batch);

    WHILE v_rows > 0 DO
        SET @stmt = v_sql;
        PREPARE stmt FROM @stmt;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;

        SET v_rows = ROW_COUNT();
        SET v_total = v_total + v_rows;

        -- 每删除 10 万条输出一次进度
        IF v_total % 100000 < p_batch THEN
            SELECT CONCAT('已删除 ', v_total, ' 条') AS progress;
        END IF;

        DO SLEEP(p_sleep);
    END WHILE;

    SELECT CONCAT('删除完成,共删除 ', v_total, ' 条记录') AS result;
END$$

DELIMITER ;

4.2 调用示例

sql 复制代码
-- 场景:删除 orders 表中一年前的数据,每批 1 万条,暂停 0.5 秒
CALL batch_delete_old_data(
    'orders',
    'created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)',
    10000,
    0.5
);

预期输出:

复制代码
+---------------------+
| progress            |
+---------------------+
| 已删除 100000 条    |
+---------------------+

...(中间多轮输出)...

+------------------------------------------+
| result                                   |
+------------------------------------------+
| 删除完成,共删除 45820000 条记录          |
+------------------------------------------+

4.3 执行流程图

#mermaid-svg-tLJcBx3mulfPK5Mi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tLJcBx3mulfPK5Mi .error-icon{fill:#552222;}#mermaid-svg-tLJcBx3mulfPK5Mi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tLJcBx3mulfPK5Mi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tLJcBx3mulfPK5Mi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tLJcBx3mulfPK5Mi .marker.cross{stroke:#333333;}#mermaid-svg-tLJcBx3mulfPK5Mi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tLJcBx3mulfPK5Mi p{margin:0;}#mermaid-svg-tLJcBx3mulfPK5Mi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster-label text{fill:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster-label span{color:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster-label span p{background-color:transparent;}#mermaid-svg-tLJcBx3mulfPK5Mi .label text,#mermaid-svg-tLJcBx3mulfPK5Mi span{fill:#333;color:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi .node rect,#mermaid-svg-tLJcBx3mulfPK5Mi .node circle,#mermaid-svg-tLJcBx3mulfPK5Mi .node ellipse,#mermaid-svg-tLJcBx3mulfPK5Mi .node polygon,#mermaid-svg-tLJcBx3mulfPK5Mi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tLJcBx3mulfPK5Mi .rough-node .label text,#mermaid-svg-tLJcBx3mulfPK5Mi .node .label text,#mermaid-svg-tLJcBx3mulfPK5Mi .image-shape .label,#mermaid-svg-tLJcBx3mulfPK5Mi .icon-shape .label{text-anchor:middle;}#mermaid-svg-tLJcBx3mulfPK5Mi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tLJcBx3mulfPK5Mi .rough-node .label,#mermaid-svg-tLJcBx3mulfPK5Mi .node .label,#mermaid-svg-tLJcBx3mulfPK5Mi .image-shape .label,#mermaid-svg-tLJcBx3mulfPK5Mi .icon-shape .label{text-align:center;}#mermaid-svg-tLJcBx3mulfPK5Mi .node.clickable{cursor:pointer;}#mermaid-svg-tLJcBx3mulfPK5Mi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tLJcBx3mulfPK5Mi .arrowheadPath{fill:#333333;}#mermaid-svg-tLJcBx3mulfPK5Mi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tLJcBx3mulfPK5Mi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tLJcBx3mulfPK5Mi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tLJcBx3mulfPK5Mi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tLJcBx3mulfPK5Mi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tLJcBx3mulfPK5Mi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster text{fill:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi .cluster span{color:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tLJcBx3mulfPK5Mi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tLJcBx3mulfPK5Mi rect.text{fill:none;stroke-width:0;}#mermaid-svg-tLJcBx3mulfPK5Mi .icon-shape,#mermaid-svg-tLJcBx3mulfPK5Mi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tLJcBx3mulfPK5Mi .icon-shape p,#mermaid-svg-tLJcBx3mulfPK5Mi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tLJcBx3mulfPK5Mi .icon-shape .label rect,#mermaid-svg-tLJcBx3mulfPK5Mi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tLJcBx3mulfPK5Mi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tLJcBx3mulfPK5Mi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tLJcBx3mulfPK5Mi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



CALL batch_delete_old_data
初始化计数器
执行 DELETE LIMIT N
删除行数 > 0?
累加计数器
累计 mod 10万 < batch?
输出进度
SLEEP 暂停
输出总删除条数
结束

4.4 关键参数调优

参数 建议值 说明
p_batch 5000~50000 太小效率低,太大锁表时间长。推荐从 10000 开始,观察负载后调整
p_sleep 0.5~2.0 有从库时建议 >= 1 秒。单机可以 0.5 秒
执行时间 低峰期 避开业务高峰,建议在凌晨 2:00~6:00 执行

部分 Navicat 版本的查询窗口不支持 DELIMITER 语法。三种替代方式:

  1. Navicat 命令行界面:菜单「工具 -> 命令行界面」,在终端中粘贴 SQL
  2. 外部 mysql 客户端mysql -h host -u user -p dbname < procedure.sql
  3. 简化版存储过程:不使用 DELIMITER,直接在查询窗口中分步执行

五、方案二:pt-archiver(生产环境首选)

pt-archiver 是 Percona Toolkit 提供的专业归档工具,天然支持分批删除,对主从延迟有内置保护。

5.1 安装

bash 复制代码
# macOS
brew install percona-toolkit

# Ubuntu/Debian
apt-get install percona-toolkit

# CentOS/RHEL
yum install percona-toolkit

5.2 基本用法:直接删除

bash 复制代码
# 删除 orders 表中一年前的数据,每批 5000 条
pt-archiver \
  --source D=your_db,t=orders,h=127.0.0.1,u=root,p=your_password \
  --where "created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)" \
  --limit 5000 \
  --bulk-delete \
  --progress 10000 \
  --statistics

5.3 进阶用法:归档到另一张表

bash 复制代码
# 把旧数据归档到 orders_archive 表,而不是直接删除
pt-archiver \
  --source D=your_db,t=orders,h=127.0.0.1,u=root,p=your_password \
  --dest D=your_db,t=orders_archive \
  --where "created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)" \
  --limit 5000 \
  --bulk-insert \
  --bulk-delete \
  --progress 10000

5.4 关键参数说明

参数 说明
--limit 每批处理的行数,等同于存储过程的 LIMIT
--bulk-delete 使用批量 DELETE 而非逐行删除,效率更高
--progress N 每处理 N 行输出一次进度
--statistics 执行结束后输出统计摘要
--check-slave-lag 指定从库地址,自动检测并等待主从同步
--max-lag 主从延迟超过此秒数时自动暂停(默认 1 秒)
--dry-run 只输出将要执行的 SQL,不实际执行。强烈建议先用这个测试

5.5 pt-archiver vs 存储过程

#mermaid-svg-knZqACTl2Xj5teui{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-knZqACTl2Xj5teui .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-knZqACTl2Xj5teui .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-knZqACTl2Xj5teui .error-icon{fill:#552222;}#mermaid-svg-knZqACTl2Xj5teui .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-knZqACTl2Xj5teui .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-knZqACTl2Xj5teui .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-knZqACTl2Xj5teui .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-knZqACTl2Xj5teui .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-knZqACTl2Xj5teui .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-knZqACTl2Xj5teui .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-knZqACTl2Xj5teui .marker{fill:#333333;stroke:#333333;}#mermaid-svg-knZqACTl2Xj5teui .marker.cross{stroke:#333333;}#mermaid-svg-knZqACTl2Xj5teui svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-knZqACTl2Xj5teui p{margin:0;}#mermaid-svg-knZqACTl2Xj5teui .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-knZqACTl2Xj5teui .cluster-label text{fill:#333;}#mermaid-svg-knZqACTl2Xj5teui .cluster-label span{color:#333;}#mermaid-svg-knZqACTl2Xj5teui .cluster-label span p{background-color:transparent;}#mermaid-svg-knZqACTl2Xj5teui .label text,#mermaid-svg-knZqACTl2Xj5teui span{fill:#333;color:#333;}#mermaid-svg-knZqACTl2Xj5teui .node rect,#mermaid-svg-knZqACTl2Xj5teui .node circle,#mermaid-svg-knZqACTl2Xj5teui .node ellipse,#mermaid-svg-knZqACTl2Xj5teui .node polygon,#mermaid-svg-knZqACTl2Xj5teui .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-knZqACTl2Xj5teui .rough-node .label text,#mermaid-svg-knZqACTl2Xj5teui .node .label text,#mermaid-svg-knZqACTl2Xj5teui .image-shape .label,#mermaid-svg-knZqACTl2Xj5teui .icon-shape .label{text-anchor:middle;}#mermaid-svg-knZqACTl2Xj5teui .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-knZqACTl2Xj5teui .rough-node .label,#mermaid-svg-knZqACTl2Xj5teui .node .label,#mermaid-svg-knZqACTl2Xj5teui .image-shape .label,#mermaid-svg-knZqACTl2Xj5teui .icon-shape .label{text-align:center;}#mermaid-svg-knZqACTl2Xj5teui .node.clickable{cursor:pointer;}#mermaid-svg-knZqACTl2Xj5teui .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-knZqACTl2Xj5teui .arrowheadPath{fill:#333333;}#mermaid-svg-knZqACTl2Xj5teui .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-knZqACTl2Xj5teui .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-knZqACTl2Xj5teui .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-knZqACTl2Xj5teui .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-knZqACTl2Xj5teui .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-knZqACTl2Xj5teui .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-knZqACTl2Xj5teui .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-knZqACTl2Xj5teui .cluster text{fill:#333;}#mermaid-svg-knZqACTl2Xj5teui .cluster span{color:#333;}#mermaid-svg-knZqACTl2Xj5teui div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-knZqACTl2Xj5teui .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-knZqACTl2Xj5teui rect.text{fill:none;stroke-width:0;}#mermaid-svg-knZqACTl2Xj5teui .icon-shape,#mermaid-svg-knZqACTl2Xj5teui .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-knZqACTl2Xj5teui .icon-shape p,#mermaid-svg-knZqACTl2Xj5teui .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-knZqACTl2Xj5teui .icon-shape .label rect,#mermaid-svg-knZqACTl2Xj5teui .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-knZqACTl2Xj5teui .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-knZqACTl2Xj5teui .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-knZqACTl2Xj5teui :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pt-archiver
pt-archiver 进程
SELECT 取出一批 ID
DELETE WHERE id IN ...
检测从库延迟
存储过程
客户端 CALL
数据库内循环
DELETE + SLEEP

pt-archiver 的核心优势是从库延迟保护 ------它会自动检测从库延迟并在延迟过高时暂停,存储过程需要手动调 SLEEP 时间来间接控制。


六、方案三:应用层分批删除

适合需要长期定期清理的场景,把删除逻辑写在定时任务中。

6.1 Python 示例

python 复制代码
import pymysql
import time
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)

def batch_delete(conn_params, table, condition,
                 batch_size=10000, sleep_seconds=1.0, max_total=0):
    """分批删除历史数据

    Args:
        conn_params: MySQL 连接参数字典
        table: 表名
        condition: WHERE 条件
        batch_size: 每批删除条数
        sleep_seconds: 批间暂停秒数
        max_total: 最多删除条数,0 为不限制
    """
    conn = pymysql.connect(**conn_params)
    cursor = conn.cursor()
    total_deleted = 0

    try:
        while True:
            sql = f"DELETE FROM {table} WHERE {condition} LIMIT {batch_size}"
            cursor.execute(sql)
            conn.commit()

            rows = cursor.rowcount
            if rows == 0:
                break

            total_deleted += rows
            logger.info(f"本批删除 {rows} 条,累计 {total_deleted} 条")

            if max_total > 0 and total_deleted >= max_total:
                logger.info(f"已达到 max_total={max_total},停止")
                break

            time.sleep(sleep_seconds)

    except Exception as e:
        conn.rollback()
        logger.error(f"删除中断,已回滚最后一批。累计 {total_deleted} 条。错误: {e}")
        raise
    finally:
        cursor.close()
        conn.close()

    logger.info(f"删除完成,共删除 {total_deleted} 条记录")
    return total_deleted


# 调用示例
batch_delete(
    conn_params={
        'host': '127.0.0.1',
        'user': 'root',
        'password': 'your_password',
        'database': 'your_db',
    },
    table='orders',
    condition="created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)",
    batch_size=10000,
    sleep_seconds=1.0,
)

6.2 执行效果

复制代码
2025-07-06 02:00:01 本批删除 10000 条,累计 10000 条
2025-07-06 02:00:02 本批删除 10000 条,累计 20000 条
2025-07-06 02:00:03 本批删除 10000 条,累计 30000 条
...
2025-07-06 04:35:12 本批删除 3200 条,累计 45823200 条
2025-07-06 04:35:12 删除完成,共删除 45823200 条记录

6.3 与定时任务集成

python 复制代码
# crontab: 每天凌晨 3 点执行
# 0 3 * * * /usr/bin/python3 /opt/scripts/clean_old_orders.py

# 或者用 APScheduler 在应用内调度
from apscheduler.schedulers.blocking import BlockingScheduler

scheduler = BlockingScheduler()

@scheduler.scheduled_job('cron', hour=3, minute=0)
def nightly_cleanup():
    batch_delete(
        conn_params={
            'host': '127.0.0.1',
            'user': 'app',
            'password': '***',
            'database': 'prod',
        },
        table='orders',
        condition="created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)",
        batch_size=5000,
        sleep_seconds=2.0,
    )

scheduler.start()

七、方案四:分区表 DROP PARTITION(最快最干净)

如果你的表已经按时间做了分区,这是最推荐 的方案------DROP PARTITION 是元数据操作,瞬间完成,不产生大量 binlog,不影响主从同步。

7.1 前提:表已按时间分区

sql 复制代码
-- 按月分区的表示例
CREATE TABLE orders (
    id BIGINT AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2),
    created_at DATETIME NOT NULL,
    PRIMARY KEY (id, created_at),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202301 VALUES LESS THAN (202302),
    PARTITION p202302 VALUES LESS THAN (202303),
    -- ... 每月一个分区
    PARTITION p202412 VALUES LESS THAN (202501),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

7.2 删除操作:一条命令搞定

sql 复制代码
-- 删除 2023 年 1 月的分区(瞬间完成)
ALTER TABLE orders DROP PARTITION p202301;

-- 删除多个分区
ALTER TABLE orders DROP PARTITION p202301, p202302, p202303;

7.3 分区方案 vs 分批删除

维度 DROP PARTITION 分批 DELETE
执行速度 毫秒级 数小时
主从影响 几乎无 需控制节奏
空间回收 自动释放 需额外 OPTIMIZE
前提条件 表必须已分区 无特殊要求
粒度 按分区粒度(月/周) 任意条件

注意: DROP PARTITION 不可逆。执行前务必确认分区范围正确,建议先 SELECT COUNT(*) FROM orders PARTITION (p202301) 验证。


八、删除后:回收磁盘空间

不管用哪种 DELETE 方案,InnoDB 删除数据后不会自动释放磁盘空间(数据页标记为可复用,但不归还给操作系统)。如果需要真正释放空间:

8.1 方法一:OPTIMIZE TABLE

sql 复制代码
-- 简单直接,但会锁表(MySQL 5.6+ 支持 Online DDL,锁表时间大幅缩短)
OPTIMIZE TABLE orders;

8.2 方法二:ALTER TABLE 重建

sql 复制代码
-- 等效于 OPTIMIZE,通过重建表释放空间
ALTER TABLE orders ENGINE=InnoDB;

8.3 方法三:pt-online-schema-change(生产推荐)

bash 复制代码
# 不锁表重建,适合生产环境
pt-online-schema-change \
  --alter "ENGINE=InnoDB" \
  D=your_db,t=orders \
  --execute

8.4 验证空间回收

sql 复制代码
-- 查看表占用的磁盘空间
SELECT
    table_name,
    ROUND(data_length / 1024 / 1024, 2) AS data_mb,
    ROUND(index_length / 1024 / 1024, 2) AS index_mb,
    ROUND(data_free / 1024 / 1024, 2) AS free_mb
FROM information_schema.tables
WHERE table_schema = 'your_db'
  AND table_name = 'orders';

data_free 字段反映碎片空间。OPTIMIZE 前后对比此值,即可确认空间是否回收成功。


九、常见问题与排障

问题 原因 解决方案
存储过程执行数小时没完 待删数据量极大(数千万) 属正常现象。通过监控 SQL 确认进度,耐心等待
DELIMITER 语法报错 Navicat 查询窗口不支持 改用「工具 -> 命令行界面」或外部 mysql 客户端
从库延迟告警飙升 binlog 写入速度超过从库回放速度 增大 SLEEP 时间至 2~5 秒;或改用 pt-archiver 的 --check-slave-lag
删除速度越来越慢 越到后面符合条件的行越稀疏 属正常现象,不影响最终结果
Navicat 查询超时断开 存储过程执行时间超过客户端超时限制 连接设置中调大超时,或改用 nohup mysql ... & 后台执行
Lock wait timeout exceeded 其他事务持有了待删行的锁 在低峰期执行;减小 batch_size;排查长事务
删除后表空间没变小 InnoDB DELETE 不释放磁盘空间 执行 OPTIMIZE TABLE 或 pt-online-schema-change 重建表
pt-archiver 报 No index found WHERE 条件字段没有索引 先给条件字段加索引

十、总结与选型建议

决策总结

#mermaid-svg-bFOLB2Grq0ZUAAIG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bFOLB2Grq0ZUAAIG .error-icon{fill:#552222;}#mermaid-svg-bFOLB2Grq0ZUAAIG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bFOLB2Grq0ZUAAIG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .marker.cross{stroke:#333333;}#mermaid-svg-bFOLB2Grq0ZUAAIG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bFOLB2Grq0ZUAAIG p{margin:0;}#mermaid-svg-bFOLB2Grq0ZUAAIG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster-label text{fill:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster-label span{color:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster-label span p{background-color:transparent;}#mermaid-svg-bFOLB2Grq0ZUAAIG .label text,#mermaid-svg-bFOLB2Grq0ZUAAIG span{fill:#333;color:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .node rect,#mermaid-svg-bFOLB2Grq0ZUAAIG .node circle,#mermaid-svg-bFOLB2Grq0ZUAAIG .node ellipse,#mermaid-svg-bFOLB2Grq0ZUAAIG .node polygon,#mermaid-svg-bFOLB2Grq0ZUAAIG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .rough-node .label text,#mermaid-svg-bFOLB2Grq0ZUAAIG .node .label text,#mermaid-svg-bFOLB2Grq0ZUAAIG .image-shape .label,#mermaid-svg-bFOLB2Grq0ZUAAIG .icon-shape .label{text-anchor:middle;}#mermaid-svg-bFOLB2Grq0ZUAAIG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .rough-node .label,#mermaid-svg-bFOLB2Grq0ZUAAIG .node .label,#mermaid-svg-bFOLB2Grq0ZUAAIG .image-shape .label,#mermaid-svg-bFOLB2Grq0ZUAAIG .icon-shape .label{text-align:center;}#mermaid-svg-bFOLB2Grq0ZUAAIG .node.clickable{cursor:pointer;}#mermaid-svg-bFOLB2Grq0ZUAAIG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .arrowheadPath{fill:#333333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bFOLB2Grq0ZUAAIG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bFOLB2Grq0ZUAAIG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bFOLB2Grq0ZUAAIG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster text{fill:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG .cluster span{color:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bFOLB2Grq0ZUAAIG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bFOLB2Grq0ZUAAIG rect.text{fill:none;stroke-width:0;}#mermaid-svg-bFOLB2Grq0ZUAAIG .icon-shape,#mermaid-svg-bFOLB2Grq0ZUAAIG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bFOLB2Grq0ZUAAIG .icon-shape p,#mermaid-svg-bFOLB2Grq0ZUAAIG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bFOLB2Grq0ZUAAIG .icon-shape .label rect,#mermaid-svg-bFOLB2Grq0ZUAAIG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bFOLB2Grq0ZUAAIG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bFOLB2Grq0ZUAAIG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bFOLB2Grq0ZUAAIG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
没有


没有

清理大表历史数据
表有分区?
DROP PARTITION (最快最干净)
一次性清理?
有 Percona Toolkit?
pt-archiver (生产首选)
存储过程 (最快上手)
应用层定时任务 (长期方案)

关键原则

  1. 永远不要一条 DELETE 删千万行数据------分批 + 暂停是铁律
  2. 操作前必须做安全检查------确认索引、确认数量、确认备份
  3. 低峰期执行------凌晨 2:00~6:00 是黄金窗口
  4. 删除后回收空间------OPTIMIZE TABLE 或 pt-online-schema-change
  5. 长期方案考虑分区表------如果经常需要按时间清理,建分区表是一劳永逸的方案

附录

相关工具与文档

资源 说明
Percona Toolkit pt-archiver 官方下载与文档
MySQL DELETE 语法 DELETE 语句完整参考
MySQL 分区表 分区表设计与使用指南
InnoDB 空间回收 OPTIMIZE TABLE 官方文档

术语表

术语 含义
binlog MySQL 二进制日志,记录所有数据变更,用于主从复制和数据恢复
undo log 事务回滚日志,DELETE 时记录被删行的旧值
redo log 事务持久化日志,确保崩溃恢复时数据不丢失
主从延迟 从库回放 binlog 的速度落后于主库写入的速度
pt-archiver Percona Toolkit 中的归档工具,支持分批删除和归档
相关推荐
aidou13142 小时前
Kotlin中沉浸式状态栏显示布局
android·kotlin·windowmanager·沉浸式状态栏·insetslistener·insetscompat·systembars
数据库小学妹2 小时前
MySQL慢SQL优化实战指南:EXPLAIN分析+索引优化+8个真实案例
sql·mysql·mysql优化·数据库性能·索引优化
无小道2 小时前
Redis——redis和mysql的数据同步
数据库·redis·mysql
阿pin3 小时前
Android随笔-pipe是什么?
android·linux·pipe
帅次3 小时前
Android 高级工程师面试:Flow 与状态流 近1年高频追问 22 题
android·面试·职场和发展
雨季余静3 小时前
RustDesk Android APK 从零编译全步骤
android
辰合软件3 小时前
通达OA目录结构与核心文件解析
android·数据库·oracle
通玄3 小时前
Jetpack Compose 入门系列(八):Compose 中的副作用处理
android
流氓也是种气质 _Cookie4 小时前
如何创建MCP
android·adb