在业务系统运行一段时间后,订单、日志类大表往往会积累海量历史记录。将这些不常访问的冷数据从主库剥离并导出到文件或备库,就是典型的MySQL归档数据导出。本文围绕实操步骤,介绍几种稳定可用的导出方式,并分析各自的适用场景与注意点。

一、明确归档范围与前置准备
动手导出之前,首先要确认需要归档的时间区间或业务状态。通常我们会以创建时间字段(如create_time)小于某个临界值作为条件。明确范围可以避免误删或漏导,也方便后续校验数据总量。建议先执行count查询确认待归档行数,做到心里有数。
另外,归档操作应尽量避开业务高峰。即便使用非锁表方式,大量磁盘IO也可能影响线上响应。最好准备一个拥有SELECT权限的只读账号,并在备库或延迟从库上执行导出,降低对主库的压力。若必须在主库操作,请务必开启事务一致性快照。
-- 确认2022年之前的订单数量 SELECT COUNT(*) FROM orders WHERE create_time < '2022-01-01 00:00:00'; -- 使用只读账号查看表结构 SHOW CREATE TABLE ordersG
二、使用mysqldump按条件导出
mysqldump是MySQL官方自带的逻辑备份工具,支持通过where参数筛选行。它生成的SQL文件可以直接在目标库执行恢复,非常适合中小规模归档。配合--single-transaction参数,能在InnoDB表上实现无锁一致性导出。
需要注意,where条件里的字符串必须正确转义,且尽量使用主键或索引字段过滤,否则可能触发全表扫描。如果表很大,建议按月份分多次导出,每次生成一个独立文件,便于并行传输与校验。下面示例导出2021年的数据:
mysqldump -u archive_ro -p --single-transaction --no-create-info --where="create_time >= '2021-01-01 00:00:00' AND create_time < '2022-01-01 00:00:00'" mydb orders > orders_2021.sql # 压缩归档文件 gzip orders_2021.sql
这种方式的优点是简单、可追溯,导出的SQL humans可读。缺点是在超大数据量时单进程速度偏慢,且恢复时需逐条insert。若归档后还要做分析,纯SQL不如CSV友好。
三、用SELECT INTO OUTFILE导出为文本
当目标仅是冷存或导入数据仓库时,文本格式(CSV/TSV)效率更高。MySQL提供SELECT ... INTO OUTFILE语法,由服务端直接写文件,速度远快于客户端转发。文件默认生成在数据库服务器本地,需注意secure_file_priv目录限制。
导出时应显式指定字段分隔符与换行符,保证和后续LOAD DATA INFILE对称。如果文件要跨机器取走,可再配合scp传输。下方代码演示导出为逗号分隔文件:
SELECT * INTO OUTFILE '/var/lib/mysql-files/orders_2020.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY 'n' FROM orders WHERE create_time < '2021-01-01 00:00:00';
该方法的性能优势明显,但不具备表结构信息,且服务端文件权限管理要严格。若secure_file_priv为空,则禁止导出,需修改配置文件并重启。对于需要长期保存的归档,建议连同建表语句一起保管。
四、编写程序分批游标导出
当单条SQL容易超时或内存吃紧时,可以用Java、Python等语言写脚本,按主键区间循环拉取。每次取一千到五千行,处理完再偏移,避免长事务。这样即使中途断点也能从最后位点续传。
以下Python示例利用分页查询将归档数据写成本地JSON行文件,适合异构系统消费。通过绑定变量防止注入,并用连接池控制并发:
import pymysql
conn = pymysql.connect(host='127.0.0.1', user='archive_ro', password='pwd', db='mydb')
cur = conn.cursor()
last_id = 0
batch = 2000
while True:
cur.execute(
"SELECT id, create_time, amount FROM orders "
"WHERE id > %s AND create_time < '2020-01-01' "
"ORDER BY id LIMIT %s", (last_id, batch))
rows = cur.fetchall()
if not rows:
break
with open('archive.jsonl', 'a') as f:
for r in rows:
f.write(str(r) + 'n')
last_id = rows[-1][0]
cur.close()
conn.close()
程序化导出灵活度最高,可以顺带做字段脱敏或格式转换。但开发维护成本也高,小型团队用前两种通常就够了。无论哪种方式,完成后都要对比源库count与导出文件行数。
五、导出后校验与清理
归档文件生成后,第一步是校验完整性。对于SQL文件可统计INSERT条数,对于CSV可用wc -l看行数,并与最初count结果核对。差异过大时要复查条件或重新导出。
确认无误且备份留存后,再考虑从原表删除已归档数据。删除也要分批,用LIMIT控制每次影响行数,防止主从延迟。若使用pt-archiver这类工具,它能边导边删且限速,是生产环境常用选择。整个过程建议记录操作日志,便于审计。
| 方案 | 是否锁表 | 速度 | 适用规模 |
|---|---|---|---|
| mysqldump带where | 否(InnoDB快照) | 中 | 中小表 |
| SELECT OUTFILE | 否 | 快 | 大表文本归档 |
| 脚本分批 | 否 | 可控 | 超大数据/定制 |
综上,MySQL归档数据导出并没有唯一标准答案。理解业务冷热度、评估停机容忍度,才能选出既安全又高效的步骤组合。