MySQL 备份表的四种方式:逻辑备份与物理备份实操指南
发布时间:2026/10/22 5:48:32 锦皓数字建站

MySQL 四种备份表的方式这件事说大不大说小不小。我见过不少朋友业务跑得好好的一提到“备份”就只会对着整个实例跑一次 mysqldump导入导出都是全库级别。但真到了实际项目里很多需求其实只想单独处理一张表比如把一张几千万行的大表归档出去把测试环境的某个配置表同步到生产又或者误删了一部分数据想从昨天的备份里把这张表捞回来。这时候如果只会整库备份不仅耗时恢复流程也会被一堆无关数据拖得很重。今天我把这些年用过的 MySQL 备份表方式整理了一遍重点讲四种mysqldump 逻辑备份、SELECT INTO OUTFILE 导出、CREATE TABLE AS SELECT 复制、物理文件备份冷备/热备。每一种我都会说清楚它适合什么场景、命令怎么写、恢复怎么做以及我踩过的坑。不管你是运维、开发还是刚接触 MySQL 的入门读者这份清单可以直接照着用。1. 先想清楚备份一张表到底要解决什么问题动手之前我强烈建议先花两分钟想一想你备份这张表究竟是为了什么因为不同的目的对应的最优方案完全不同。我总结了最常见的几类场景表结构变更之前给原表留一个快照方便回滚。把线上大表的部分数据归档到本地释放生产空间。把一张表从 A 环境完整搬到 B 环境要求结构、数据、索引都一致。误操作删了数据想从某个备份里恢复这张表的特定时间点状态。临时给分析同事导出一份 CSV 做数据报表。这些需求看似都是“备份一张表”但对速度、一致性、跨版本兼容性的要求差别很大。只是导一份 CSV 给分析师SELECT INTO OUTFILE 就够了如果是结构变更前做保险mysqldump 或物理快照更稳如果要做跨环境迁移还得考虑目标版本、字符集、索引这些额外因素。我评估一个备份方案习惯用四个维度一致性即备份出来的数据是不是某个时间点上的完整状态完整性有没有丢行、丢列可恢复性拿到备份之后能不能快速、顺利地恢复业务成本包括耗时、磁盘占用、对线上实例的影响。这四个维度往往互相矛盾想面面俱到很难。下面四种方式的对比也都是围绕这四点展开的。四种方式先看一张总览表备份方式核心原理数据类型一致性跨版本典型场景mysqldump导出 SQL 语句逻辑备份高配 single-transaction高结构迁移、小表备份OUTFILE / LOAD DATA导出文本文件逻辑备份中中数据交换、批量导入CTAS / INSERT SELECTSQL 复制数据逻辑备份中高同库建临时表、归档物理文件备份拷贝数据文件物理备份冷备高 / 热备高低大数据量整体迁移提示一致性这个词很关键。物理备份拷贝的是原始数据文件逻辑备份拷的是 SQL 或文本两者在恢复方式上有本质区别。选错方案关键时刻会非常被动。2. 方式一mysqldump最通用的逻辑备份mysqldump 是 MySQL 自带的最直观的工具也是绝大多数人接触的第一种备份方式。它的原理很简单把表的结构和数据翻译成一条条 SQL 语句写进一个文本文件。恢复的时候执行这个 SQL 文件表就重新建起来了数据也重新插进去了。先看最常用的几条命令# 备份一张完整的表结构 数据 mysqldump -u root -p --single-transaction mydb user_info user_info.sql # 只要表结构不要数据 mysqldump -u root -p --no-data mydb user_info user_info_schema.sql # 只要数据不要建表语句 mysqldump -u root -p --no-create-info mydb user_info user_info_data.sql--single-transaction 这个参数建议在 InnoDB 表上使用。它通过开启一个 REPEATABLE READ 事务拿到一致性快照导出过程中不会锁住整张表业务可以继续读写。如果是 MyISAM 引擎这个参数不生效mysqldump 会执行 LOCK TABLES这时候线上写操作会被阻塞。所以备份前确认一下表的存储引擎很重要。再来看带条件的备份。假设用户表很大只备份最近七天的数据mysqldump -u root -p --single-transaction \ --wherecreated_at 2024-06-01 00:00:00 AND created_at 2024-06-08 00:00:00 \ mydb user_info user_info_7days.sql--where 参数非常实用。我曾在某个项目里用它做每日增量归档效果很好。但要注意两点条件里的字段必须在目标表里存在写法是标准 SQL 条件不是程序里那种拼接变量。拿到一张大表想导出一部分数据时这个参数比全表导出再删要靠谱得多。恢复很简单mysql -u root -p mydb user_info.sql # 或者进入 MySQL 客户端后执行 mysql source /path/to/user_info.sql如果你用 --where 导出的数据要恢复到一张已经存在的表需要先确认表结构一致否则执行时遇到表已存在会直接报错。这里补充一个我常用的细节导出数据文件时开头不要带 CREATE TABLE 语句恢复前先手动建好目标表结构完全由自己掌控。用 mysqldump 之前有三件事必须确认。第一字符集。库里有中文甚至表情符号时导出一定要显式指定mysqldump -u root -p --default-character-setutf8mb4 mydb user_info user_info.sql不指定的情况下某些老版本会按 latin1 或 utf8 导出恢复时出现乱码我在这上面栽过一次教训很深。第二磁盘空间。逻辑备份的 SQL 文件体积往往是表实际数据的好几倍。每条 INSERT 语句都带字段名和额外语法加上事务控制、注释等内容非常膨胀。导出大表之前先确认目标目录的剩余空间别写到一半报 disk full这是最尴尬的事。第三触发器、存储过程要不要一起备份。做完整迁移时要加 --routines --triggers如果只是临时备份数据反而不要带否则恢复时可能因为权限或依赖问题报错。最后重点提醒一句mysqldump 对大表非常不友好。一张表几个 GB 甚至几十 GB导出可能需要几十分钟恢复更慢因为本质是逐条执行 INSERT性能远低于直接拷贝文件。生产环境的大表备份我一般不建议用 mysqldump而是推荐后面的物理备份方案。3. 方式二SELECT INTO OUTFILE LOAD DATA INFILE文本交换利器这种方式很多人可能没怎么用过但做数据交换时相当顺手。它导出的不是 SQL而是纯文本文件默认每行一条记录字段间用制表符分隔。你可以指定成 CSV 格式直接交给分析同事或导入 Excel。导出命令SELECT * FROM mydb.user_info INTO OUTFILE /var/lib/mysql-files/user_info.csv FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n;几个参数是关键。FIELDS TERMINATED BY , 表示字段之间用逗号分隔OPTIONALLY ENCLOSED BY 表示字符串类型的字段用双引号包起来即使内容里带逗号也不会被误解成字段分隔符LINES TERMINATED BY \n 表示每行以换行符结尾。导入命令LOAD DATA INFILE /var/lib/mysql-files/user_info.csv INTO TABLE mydb.user_info_copy FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n;如果目标表已经存在LOAD DATA 会往里面追加数据不会自动清空旧数据。所以想做“整表替换”要先 TRUNCATE 目标表再 LOAD。这顺序我建议固定下来TRUNCATE 之后立刻 LOAD中间不留空隙避免外部程序读到一半状态的数据。这里有两个坑必须提。第一个是 secure-file-priv。MySQL 5.7.6 之后出于安全考虑默认限制了 INTO OUTFILE 和 LOAD DATA 的目录。导出时如果报错ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement就是它拦的。查看当前限制目录SHOW VARIABLES LIKE secure_file_priv;如果结果是 /var/lib/mysql-files/导出路径必须在这个目录下如果结果是 NULL表示禁止导入导出需要修改配置文件后重启 MySQL。我的习惯是每台机器维护一个固定的导出导入目录路径写死权限也干净避免每次纠结。第二个是字符集。导出时默认使用客户端连接的字符集。如果连接时没指定 utf8mb4导出文件里的中文会变成乱码。稳妥做法是在连接串里统一设置mysql -h host -u root -p --default-character-setutf8mb4另外还要注意 NULL 值的处理。SELECT INTO OUTFILE 会把 NULL 转成 \N 这个转义字符LOAD DATA 导入时又能识别回 NULL。如果你导出后手动编辑过文件不小心把 \N 删掉这一列就会变成空字符串导致数据不一致。所以我一般建议导出的文件尽量直接使用真要手动改也得先明确这个规则。什么场景推荐用它我最常用的是两个跨库、跨实例的数据交换以及给外部系统导 CSV。它的速度比 mysqldump 快不少因为不需要生成 SQL 文本恢复也是批量写入。但短板也很明显不备份索引和表结构需要另外维护结构定义一致性不够强导出那一瞬间如果有人正在写这张表可能读到部分新数据。所以导出前最好选在业务低峰期。4. 方式三CREATE TABLE AS SELECT 与 INSERT INTO SELECT同库快速复制CTAS 这个方式看起来不像传统备份但实际场景里做临时表、归档表非常顺手。最大的优点是速度快、语法简单一条 SQL 搞定不需要额外工具。最基本的用法-- 完整复制一份 CREATE TABLE user_info_bak AS SELECT * FROM user_info; -- 只复制部分数据 CREATE TABLE user_info_archive AS SELECT * FROM user_info WHERE last_login 2024-01-01; -- 只要结构不要数据 CREATE TABLE user_info_empty AS SELECT * FROM user_info WHERE 10;CTAS 有一个特别容易踩的坑它只复制数据不会复制索引、主键、自增属性、默认值、外键约束。也就是说原表有主键和索引新表复制完是一张没有索引的裸表。需要自己补上ALTER TABLE user_info_bak ADD PRIMARY KEY (id); ALTER TABLE user_info_bak ADD INDEX idx_created_at (created_at);这个细节非常重要。我在某个项目里做过在线归档用 CTAS 把近一年的订单导到归档表当时赶时间没补索引结果查询归档数据慢到怀疑人生后来才发现索引丢了。另一个更灵活的组合写法是先建一张和原表结构一致的空表再单独导数据-- 先复制结构包括索引、约束 CREATE TABLE user_info_bak LIKE user_info; -- 再插入数据 INSERT INTO user_info_bak SELECT * FROM user_info WHERE ...;CREATE TABLE LIKE 会把索引、自增属性、默认值都带过来这点和 CTAS 完全不同。所以如果目标是“生成一张跟原表一模一样结构的表”我永远推荐先用 LIKE 建表再用 INSERT SELECT 填数据。如果想刻意测试某条 SQL 在无索引情况下的性能那反而可以用 CTAS。使用 CTAS 时还要注意锁和空间。CREATE TABLE AS SELECT 在执行瞬间会持有表上的元数据锁虽然 InnoDB 下大部分时间不会阻塞读写但大表执行期间 DDL 和 DML 的调度还是会受到一些影响。复制数据本身也是一次大范围读取加写入I/O 压力会明显上升。业务高峰期不要做这种事宁可放到凌晨跑。场景上我推荐三种情况优先选择 CTAS 组合拳给一张大表的某个分区做独立归档。在同一个库里快速生成测试数据副本。结构变更之前给原表拍一张“逻辑快照”。尤其最后一种如果你接下来要对表做大规模 ALTER比如改字段类型、加分区先 CREATE TABLE xxx_bak LIKE 原表再 INSERT INTO xxx_bak SELECT 数据很快能生成一个可回滚的备份。要回滚时把原表 DROP再把备份表 RENAME 回原表名即可。5. 方式四物理文件备份冷备与热备物理备份和前面三种最本质的区别是前面导出的是逻辑数据物理备份直接拷贝 MySQL 磁盘上的数据文件。优点是速度极快缺点也很明显——通常只能用在同版本、同平台的 MySQL 之间而且拷贝过程中必须保证数据文件一致。冷备份是最容易理解的物理备份先停 MySQL 服务把整个数据目录打包再启动服务。systemctl stop mysqld tar -czf /backup/mysql_full_$(date %F).tar.gz /var/lib/mysql systemctl start mysqld服务已停止磁盘上的文件就是完全一致的状态。备份拿到任何机器上只要版本和路径匹配都能直接启动。这个方案很笨但某些场景下最稳比如机器迁移、磁盘扩容、大版本升级前想给整个实例留一份完整档案。缺点也很明显停机期间业务不可用对 7x24 小时的系统不现实。热备方案业内主流是 Percona XtraBackup。原理比较复杂简单说就是通过复制数据文件加解析 redo log在 MySQL 运行期间也能生成一致性的备份。基本流程分几步。第一步备份。建议把备份目录和日志目录区分开避免把日志也一起备份进去xtrabackup --backup --target-dir/data/backup/mysql_$(date %F) \ --userbackup_user --passwordxxx第二步准备prepare。这一步不能省略。它的作用是应用 redo log让文件达到一致状态xtrabackup --prepare --target-dir/data/backup/mysql_$(date %F)第三步恢复。停止 MySQL 后把目录 copy 回去systemctl stop mysqld xtrabackup --copy-back --target-dir/data/backup/mysql_$(date %F) chown -R mysql:mysql /var/lib/mysql systemctl start mysqldchown 这里不要漏。我之前有一次恢复完忘记修改属主MySQL 无法启动日志里报的是权限问题排查了好一会儿才反应过来。XtraBackup 最常用的场景是大数据量实例的定期全量备份。单表几百 GB 时mysqldump 基本不可用用 XtraBackup 可能几十分钟甚至更短就能完成全程不锁表。注意版本匹配MySQL 8.0 要用 XtraBackup 8.0MySQL 5.7 用 XtraBackup 2.4混用会有兼容性问题。装好之后先运行 xtrabackup --version和 MySQL 版本做个对照。物理备份还有一个特性问题跨版本恢复并不像逻辑备份那么友好。拿 MySQL 8.0 的物理备份恢复到 MySQL 5.7通常会直接失败。恢复之后数据目录路径变了还要处理配置里 datadir、log_error 等路径设置确认目录可写。6. 四种方式怎么选对比与决策建议综合对比如下这张表可以直接存下来当参考维度mysqldumpOUTFILE/LOAD DATACTAS/INSERT SELECT物理备份备份速度慢快快最快恢复速度慢快快最快是否需要停服务否InnoDB否否冷备需要热备不需要一致性保障高事务快照低读取时无快照中高跨版本迁移高中高低备份体积大较小与数据相当与原文件相当是否包含索引结构包含不包含CTAS 不包含LIKE 包含包含上手难度低低低中适用规模小表中表中表大表选型建议日常小表、结构变更前的快速快照优先 mysqldump给外部人员导数据、做 CSV 交换直接用 OUTFILE同库内做临时表或归档CTAS 最顺手超过几十 GB 的大表老老实实上 XtraBackup。还有一个组合拳思路全量备份加 binlog 增量。我维护的一套核心业务库每天凌晨 2 点用 XtraBackup 做一次全量备份同时开启 binlog 并设置 7 天过期。如果某天上午 11 点误删了一张表恢复流程是先把昨晚的 XtraBackup 备份恢复到一台临时实例然后用 mysqlbinlog 把 2 点到 11 点之间的 binlog 在临时实例上回放生成一个接近删除前状态的表最后再导出这张表回到生产。这套流程基本覆盖了绝大多数灾难恢复场景。mysqldump 也支持这种玩法导出时带 --master-data2就能记录当时的 binlog 坐标后面做增量恢复时位置就有了依据。7. 实际项目中踩过的几个大坑这个话题我真是用实际代价换来的经验。整理几个高频问题可以直接对号入座。第一个secure-file-priv 导致 OUTFILE 失败。前面提过但值得再强调一次因为报错信息不够直观。我第一次用 SELECT INTO OUTFILE 导出时明明路径没问题还是报 1290 错误查了半天才发现是参数限制。现在我的习惯是一台机器只维护一个导出导入目录SQL 模板里路径写死避免每次纠结。第二个mysqldump 导出中文乱码。有一次帮同事导用户备注表那边打开 SQL 文件看中文正常恢复后页面显示全是问号。排查下来发现是导出时客户端连接的 character_set_results 不是 utf8mb4。后来我在所有 mysqldump 导出命令里都强制加上 --default-character-setutf8mb4并在恢复端确认目标库字符集乱码问题基本绝迹。第三个恢复时报外键约束错误。整库或跨表恢复时表之间的外键依赖顺序可能会出问题。最稳妥的土办法是恢复前先关闭外键检查恢复完再打开SET FOREIGN_KEY_CHECKS 0; source user_info.sql SET FOREIGN_KEY_CHECKS 1;特别是用 mysqldump 恢复包含多张关联表的库时带上这个操作能省很多麻烦。第四个磁盘空间估算失误。逻辑备份文件大小和原数据之间关系很大INSERT 语句文本膨胀率通常在 1.5 倍到 3 倍之间。我在一次备份前看到数据目录还有 30GB觉得够了结果一张 20GB 的表导出的 SQL 文件居然有 50 多 GB写到一半直接失败。现在我会用下面这个 SQL 先快速估算表大小SELECT table_name, ROUND((data_length index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema mydb AND table_name user_info;然后根据预估体积预留至少两倍空间再动手。第五个LOAD DATA 导入时把空字符串读成了 NULL。这个不一定算 bug取决于业务怎么理解空字符串。默认情况下如果字段允许 NULLLOAD DATA 会把空字段读成 NULL。如果你的业务里“空字符串”是有意义的占位符导入后要小心。解决方法是加一个 NULLIF 处理LOAD DATA INFILE /path/file.csv INTO TABLE mydb.user_info FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n (column1, column2, ...) SET column3 NULLIF(column3, );用 NULLIF 把空字符串转回 NULL或者反过来用 IFNULL 把 NULL 转成空字符串取决于需要的语义。最后聊点个人体会。备份这东西平时感觉不到它的存在一旦出事就是天大的事。我在实际维护中发现真正靠谱的方案不是某一个单一工具而是把全量备份、binlog 增量、逻辑导出这几层组合起来形成一个体系。上面提到的四种备份表方式没有一种能覆盖所有场景但当你手里同时握着这几张牌遇到任何需求都不会慌。物理备份是我现在对大表最依赖的手段但它的学习曲线比 mysqldump 陡不少建议先在测试环境完整演练两遍再上生产。如果你现在还停留在“只会 mysqldump 一把梭”的阶段不妨挑一个周末用 CTAS 或 OUTFILE 练练手很快就会发现原来备份表还可以这么灵活。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。