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 执行 |
4.5 Navicat 中 DELIMITER 报错的替代方案
部分 Navicat 版本的查询窗口不支持 DELIMITER 语法。三种替代方式:
- Navicat 命令行界面:菜单「工具 -> 命令行界面」,在终端中粘贴 SQL
- 外部 mysql 客户端 :
mysql -h host -u user -p dbname < procedure.sql - 简化版存储过程:不使用 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 (生产首选)
存储过程 (最快上手)
应用层定时任务 (长期方案)
关键原则
- 永远不要一条 DELETE 删千万行数据------分批 + 暂停是铁律
- 操作前必须做安全检查------确认索引、确认数量、确认备份
- 低峰期执行------凌晨 2:00~6:00 是黄金窗口
- 删除后回收空间------OPTIMIZE TABLE 或 pt-online-schema-change
- 长期方案考虑分区表------如果经常需要按时间清理,建分区表是一劳永逸的方案
附录
相关工具与文档
| 资源 | 说明 |
|---|---|
| Percona Toolkit | pt-archiver 官方下载与文档 |
| MySQL DELETE 语法 | DELETE 语句完整参考 |
| MySQL 分区表 | 分区表设计与使用指南 |
| InnoDB 空间回收 | OPTIMIZE TABLE 官方文档 |
术语表
| 术语 | 含义 |
|---|---|
| binlog | MySQL 二进制日志,记录所有数据变更,用于主从复制和数据恢复 |
| undo log | 事务回滚日志,DELETE 时记录被删行的旧值 |
| redo log | 事务持久化日志,确保崩溃恢复时数据不丢失 |
| 主从延迟 | 从库回放 binlog 的速度落后于主库写入的速度 |
| pt-archiver | Percona Toolkit 中的归档工具,支持分批删除和归档 |