
做MySQL运维和开发的朋友迟早会跟mysqldump打交道。它就是MySQL自带的逻辑备份工具能把数据库里的表结构、数据、视图、存储过程这些内容按照SQL语句的形式导出成一个文本文件。这个文件你用编辑器就能打开查看后续不管是数据迁移、环境复制还是误删恢复都能靠它拉一把。这篇内容不绕弯子我会把mysqldump的常用参数、备份姿势、恢复操作、踩坑记录一次性梳理清楚适合刚接触MySQL的初学者也适合已经有几年经验但平时只用图形化工具导数据、没深究过命令行参数的同学。我在生产环境里做过不少次备份和恢复栽过的跟头也不少。这篇文章会先讲清楚它的核心原理和适用场景再给出可以直接复制使用的命令和脚本最后把排查问题的经验整理成速查表。你照着操作至少能把日常备份这件事做得规规矩矩遇到真正要恢复数据的时候心里会踏实很多。1. 先搞清楚mysqldump是干什么的1.1 逻辑备份和物理备份差在哪MySQL备份大致分两条路物理备份和逻辑备份。物理备份直接拷贝数据文件、日志文件好比把你电脑的整个硬盘拆下来存到另外一个盘里速度快、恢复快但跨版本恢复时经常出问题。而mysqldump属于逻辑备份它把数据解析成一条条INSERT语句导出的文件本质上是文本相当于把你文档里的内容打印成纸质稿换个地方再敲回电脑里。它跟MySQL的版本、存储引擎兼容性更好同样一份备份文件用在新版本或者老版本环境里可能性都比直接拷贝数据文件高得多。mysqldump的优势是灵活、可读性强。你导出的.sql文件可以直接用vim打开看看表结构写得对不对甚至只挑某几条数据手动改改再导入。这种通过SQL语句表达备份内容的方式也决定了它能做到细粒度备份可以只导一张表可以只导某个时间段的数据还可以只导表结构不要数据。这些都是物理备份不太好实现的操作。劣势也很明显慢。表数据量大时逐条INSERT的导出和导入都很耗时间全量备份一个几GB的库可能需要很长时间。所以生产环境的大库备份我通常建议用物理快照或者专门的备份工具比如Percona XtraBackup而中小型库、开发测试环境、跨版本迁移等场景mysqldump依然是最顺手的选择。1.2 什么时候该用mysqldump什么时候别用它适合用mysqldump的场景我总结下来是这几类中小型数据库的定期全量备份单库大小几个GB以内时导出和恢复的时间都可以接受。数据迁移尤其是跨MySQL大版本迁移比如5.7迁到8.0逻辑备份文件的兼容性比物理文件好处理。开发环境或测试环境需要一份脱敏后的数据子集用mysqldump加WHERE条件导出部分数据比直接copy库方便。需要把库结构分享给同事或者只在本地重建结构时用 --no-data 导出结构即可。从一台机器迁移到另一台机器但两边的文件系统、目录结构不一样时逻辑备份更省心。不适合用的场景也要明确几十GB甚至上百GB的大库不建议拿mysqldump硬扛一是导出时间长二是恢复时执行INSERT语句的耗时可能比业务预期久得多实时性要求极高的主从复制环境备份方式要配合binlog做增量切换单独用mysqldump全量备份无法覆盖增量区间。如果数据库已经损坏mysqldump可能连导出都做不了这时候物理备份文件反而是救命稻草。所以备份方案最好是组合拳mysqldump负责逻辑层的日常备份物理层再配一个快照或者XtraBackup兜底。2. 命令基础先把连接参数和核心参数吃透2.1 连接参数别偷懒全写在命令行里mysqldump本质上是MySQL客户端程序连接数据库的参数和mysql命令行是一样的。常用的几个先列出来参数说明示例-u用户名-uroot-p密码-p123456不建议这种明文方式-h主机名或IP-h127.0.0.1-P端口大写P-P3306-SSocket文件路径-S /tmp/mysql.sock--protocol连接协议--protocolsocket我不建议在命令行里直接写密码因为操作系统的history会记录服务器上的其他用户也可能通过ps命令看到。更安全的做法是只写 -p 不跟密码回车后交互式输入。如果写自动化脚本可以在配置文件里设置凭据比如在 /etc/my.cnf 的 [mysqldump] 段配置 user、password、host这样mysqldump执行时会自动读取命令本身不用暴露密码文件权限也单独设成600。2.2 核心参数逐个拆解连接参数之外决定备份内容和行为的是这些核心参数。我挑实际使用频率最高的几个讲透。--single-transaction这个参数是InnoDB表备份的“免锁神器”。它会在导出前开启一个可重复读事务利用MVCC机制保证备份期间看到的数据是某个时间点的一致性快照不锁表也不影响在线业务的读写。需要注意的是它对MyISAM表不生效MyISAM表仍然会被锁住所以如果你的库里有MyISAM表要么提前改成InnoDB要么接受备份期间这些表的读写会被阻塞。--master-data2这个参数会记录备份时刻的binlog文件名和position两台服务器做数据同步或者架构升级时非常有用的信息。等于说它会在备份文件里额外写两行注释一行是CHANGE MASTER TO一行是MASTER_LOG_FILE和MASTER_LOG_POS标注了从哪个binlog位置开始做增量同步。值设为1时生成的语句是可直接执行的值为2时语句被注释掉只做记录我一般用2防止误执行。--routines --triggers --events这三个参数分别导出存储过程/函数、触发器、事件调度器。默认情况下mysqldump是不导出这些内容的很多人备份完恢复到新环境发现少了很多业务逻辑就是因为没加这几个参数。建议备份业务库时默认都带上。--set-gtid-purgedOFFMySQL 5.6以后启用GTID默认情况下mysqldump会把GTID信息也写入备份文件。恢复到另一个实例时如果目标库的事务和源库有交集或者GTID冲突会导致恢复失败。异地恢复、复制架构调整时通常把这个参数设为OFF让备份文件不包含SET GLOBAL.GTID_PURGED语句。8.0版本里还会改成SQL注释但为了跨版本兼容我建议仍然显式指定。--hex-blob导出二进制类型字段BLOB/BINARY时默认是转义后的文本形式恢复时偶尔出现字节不一致的问题。加上 --hex-blob 后二进制数据直接用十六进制形式表示恢复更可靠。凡是库里有图片、附件、加密串这类字段备份命令必带这个参数。--where只导出符合条件的行。比如只想备份最近一周的订单可以用 --wherecreate_time 2024-01-01 00:00:00。这个参数配合单表备份非常好用也是我做数据抽样的主力工具。2.3 默认生效的 --opt 组合很多人没注意到mysqldump默认会启用一个叫 --opt 的参数组合。它不是单个参数而是下面这些参数的集合--quick不缓存查询结果逐条取数--add-drop-table在CREATE TABLE前自动加DROP TABLE IF EXISTS恢复时不用清理旧表--add-locks导出数据时在INSERT语句前后加LOCK TABLES导入更快--extended-insert多条数据合并成一条INSERT减少SQL解析时间--lock-tables备份时锁定所有表。因为是默认启用的所以你直接执行mysqldump不加任何参数也会获得这些行为。这里提醒一点如果你备份的是MyISAM表默认的--lock-tables会锁表生产环境要当心如果是InnoDB表配合--single-transaction使用即可。若你想关闭某个优化项可以在命令里加 --skip-xxxx比如关闭extended-insert用 --skip-extended-insert数据量很大时需要做文本分析和导入调试时会用到。3. 几种备份姿势从单库到全库从结构到数据3.1 单库备份最常用的入门命令备份单个库这是日常操作里出现频率最高的形式。命令格式很简单mysqldump -uroot -p --single-transaction --routines --triggers --events testdb testdb_$(date %Y%m%d).sql执行完当前目录下就会出现一个带日期的sql文件。这个命令里我没有加 --databases所以备份文件里只有建表和数据语句没有CREATE DATABASE语句。恢复时你需要自己先建库再用mysql命令导入。如果你希望备份文件自带建库语句并且恢复时能自动选择或创建对应的库就要用 --databases 参数mysqldump -uroot -p --databases testdb testdb_with_create.sql这两种写法有本质区别。前者恢复时更灵活可以随时把数据灌到任意一个新建的库里后者是把库作为整体备份恢复时直接建库并导入适合做整库迁移。我个人的习惯是默认用第一种迁移数据库到新机器时才用第二种。3.2 全库备份和数据迁移的注意点全库备份有两种做法一种是显式列出所有库名另一种是用 --all-databasesmysqldump -uroot -p --single-transaction --routines --triggers --events --all-databases all_$(date %Y%m%d).sql全库备份时mysqldump会同时导出mysql系统库和performance_schema库的一部分内容。要注意恢复全库备份文件时必须是具有足够权限的用户而且目标实例的初始化方式最好和源实例类似否则可能因为用户表、权限表导入顺序问题报错。所以我一般不建议用全库备份文件在另一台机器上做“裸还原”更稳的方式是只备份业务库权限单独用GRANT语句重建。跨版本迁移时还有一点要留意MySQL 8.0默认的认证插件是caching_sha2_password如果备份文件里包含用户表在5.7环境可能认证失败。所以跨版本迁移我强烈建议只迁业务数据不迁系统库到目标实例上重建用户和权限。3.3 单表备份和条件备份只备份一张表命令里把库名和表名都写上mysqldump -uroot -p --single-transaction testdb users users_$(date %Y%m%d).sql单表备份加条件筛选是数据抽样的常用姿势。比如业务库的order表特别大你只需要订单金额大于1000的数据做分析环境测试就可以这样写mysqldump -uroot -p --single-transaction testdb order --whereamount 1000 order_high_amount.sql这里要特别提醒--where的SQL条件MySQL会直接拼到SELECT语句里执行。如果有特殊字符比如字符串里有单引号需要小心转义。我遇到过同事因为where参数里带了不规范的空格导致备份命令直接报错的情况排查半天才明白是条件语句写错了。条件值建议用双引号包裹内部SQL条件用单引号避免shell层面的解释干扰。3.4 只备份结构或只备份数据日常维护中经常会碰到只需要表结构不需要数据的场景。比如开发环境要同步生产环境的建表语句或者你要重构库结构时留一个结构快照。此时用 --no-datamysqldump -uroot -p --no-data testdb structure_only.sql反过来只需要数据不需要结构用 --no-create-info它会跳过CREATE TABLE语句只导出INSERT语句。这个场景比较少见但在某些数据迁移工具里很实用目标表已经建好只需要灌数据mysqldump -uroot -p --single-transaction --no-create-info testdb data_only.sql这两个参数都挺好记的no-data是没数据no-create-info是没建表信息真的别搞反了我见过有人把两个参数写反导致恢复时一直报表不存在。4. 恢复实战备份只是前半场4.1 恢复前的两个习惯动作备份做得再勤不验证恢复等于白做。我每次在测试环境执行恢复前至少会做两件事。第一查看备份文件头部确认它包含哪些建库/建表语句grep一下CREATE TABLE的数量大致判断文件内容是否完整。第二在目标环境先建好空的数据库如果是覆盖恢复先确认原库真的可以丢执行DROP之前先记录当前数据和表数量万一恢复出问题还有对照。另外就是恢复顺序。如果备份文件里有外键约束导入时经常因为表间依赖关系报错。我的做法是导入前在会话里设置SET FOREIGN_KEY_CHECKS0;导入完成后再执行SET FOREIGN_KEY_CHECKS1。mysqldump默认生成的文件并不会自动加上这两行除非你是用 --opt 默认参数但--opt也不包含外键检查的开关。所以恢复有外键的库时自己手动关闭外键校验能省掉大量报错。4.2 三种恢复方式对比方式一mysql命令行重定向最常用mysql -uroot -p testdb testdb_20240101.sql方式二进入mysql客户端后用source命令mysql -uroot -p testdb source testdb_20240101.sql;source适合在交互式环境里手动恢复能看到实时的执行状态和报错。不过它的路径是相对当前工作目录的执行前先确认sql文件的位置。方式三用mysql客户端执行并输出日志mysql -uroot -p testdb testdb_20240101.sql import.log 21生产环境恢复建议用这种方式后台跑任务日志记录齐全出问题能查到具体在哪里失败。千万别在业务高峰期直接前台执行一个大文件的导入一旦卡住很难优雅地中止。4.3 恢复时最容易翻车的三个坑第一个坑是字符集不一致。源库是utf8mb4目标库默认latin1导入后中文全部乱码。解决办法是恢复前先检查目标库的字符集设置mysql -uroot -p -e SHOW VARIABLES LIKE character_set%;确保数据库、表、连接三层的字符集一致或者显式指定mysql -uroot -p --default-character-setutf8mb4 testdb backup.sql第二个坑是binlog导致的不一致。大文件导入期间主库的binlog会记录所有INSERT操作恢复耗时越长binlog增长越明显。如果你的机器硬盘不大导到一半硬盘满了恢复直接失败。所以导入大文件前先看下磁盘空间必要时配置一下目标库的binlog过期时间。第三个坑是mysqldump导出的文件里默认包含DROP TABLE语句。你在一个还有数据的库上执行恢复原表会先被删掉再重建。如果恢复失败原表已经没了后果比较严重。所以覆盖恢复前一定要先单独备份原库或者把恢复操作放到一个全新的空库中执行确认无误后再切换业务流量。5. 备份策略与自动化脚本5.1 先想清楚备份策略再写脚本备份不是每天跑一遍命令那么简单策略才是核心。我习惯先回答这几个问题容忍丢失多少数据恢复时间目标是多少有多少种故障场景需要覆盖想清楚后再选备份方式和频率。中小型业务常见的组合是每天凌晨用mysqldump做全量备份保留最近7天或14天的文件每半小时或一小时用binlog做增量备份确保能恢复到最近一个备份点之后的时间。全量备份解决“有没有基础数据”的问题增量备份解决“能不能追到最近”的问题。两者组合才算是相对完整的备份方案。5.2 一个可以直接改用的备份脚本下面这个脚本是我在多个项目里用过的简化版本功能完整直接复制后改几个变量就能用#!/bin/bash # 使用方法执行全量备份并清理超过保留天数的旧备份文件 BACKUP_DIR/data/backup/mysql MYSQL_HOST127.0.0.1 MYSQL_PORT3306 MYSQL_USERbackup_user MYSQL_PASSWORDyour_secure_password RETENTION_DAYS7 DATE_TAG$(date %Y%m%d_%H%M%S) DATABASES(testdb orderdb userdb) mkdir -p ${BACKUP_DIR} for db in ${DATABASES[]}; do mysqldump \ --single-transaction \ --routines \ --triggers \ --events \ --master-data2 \ --set-gtid-purgedOFF \ --hex-blob \ -h${MYSQL_HOST} \ -P${MYSQL_PORT} \ -u${MYSQL_USER} \ -p${MYSQL_PASSWORD} \ ${db} ${BACKUP_DIR}/${db}_${DATE_TAG}.sql 2 ${BACKUP_DIR}/${db}_${DATE_TAG}.log if [ $? -eq 0 ]; then gzip ${BACKUP_DIR}/${db}_${DATE_TAG}.sql echo $(date %F %T) ${db} backup success else echo $(date %F %T) ${db} backup failed, check log exit 1 fi done find ${BACKUP_DIR} -name *.sql.gz -mtime ${RETENTION_DAYS} -delete这个脚本做了几件事循环备份指定数据库日志单独保留一份成功后用gzip压缩减小占用空间最后按保留天数清理旧文件。一个补充细节是我给备份用户单独建了一个较低权限的账号只授予SELECT、RELOAD、PROCESS、SHOW VIEW等必备权限而不是直接用root。5.3 crontab配置和日志检查脚本写好后加到crontab里定时执行30 2 * * * /usr/local/bin/mysql_backup.sh /data/backup/backup_cron.log 21凌晨两点半执行错开业务高峰。定时任务除了执行本身我还会额外做一件事让脚本在执行完备份后把备份文件传给另一台机器或对象存储。这个可以根据公司的环境来EPEL源里有个sync工具或者是用rclone同步到云端小文件量时用rsync也很方便。重点是别把鸡蛋全放在一个篮子里服务器硬盘坏了备份文件跟着一起丢那就真的欲哭无泪了。日志检查也要养成习惯。我见过不少人的crontab写着但从来没看过crond日志结果脚本因为路径变了、磁盘满了等原因已经连续跑两周失败直到要恢复数据时才暴露。最简单的做法是在脚本里加一个结果通知失败时发邮件或者推消息。没有现成的监控平台时用文本日志加关键词搜索也行但一定要看。6. 常见问题与排查技巧实录6.1 备份文件突然变得特别大或者特别小文件特别大先检查是否把多个库备份进了同一个文件再看是否包含了binlog相关的冗余信息。文件特别小甚至只有几KB通常是备份失败了常见原因有目标表的权限不足、连接参数写错导致连上了别的库、脚本里库名拼错。遇到这种情况优先查看同目录下的log文件我习惯在脚本里把标准错误重定向到独立日志排查起来一目了然。还有一次我发现备份文件恢复了但数据比源库少了不少。后来定位是源库里存在myisam表而mysqldump在备份这些表时如果业务正在执行DML老版本工具可能抓到不一致的快照。解决办法就是备份前先确认表的存储引擎尽量统一为InnoDB或者把备份安排在业务低峰期。6.2 恢复时提示ERROR 1418或存储函数问题导入文件里如果包含存储函数且MySQL开启了log_bin_trust_function_creators0默认可能是1恢复时可能会报错。这是开发环境相对常见的问题因为生产环境很少让人随便创建存储函数。解决办法是恢复前设置SET GLOBAL log_bin_trust_function_creators 1;恢复完再改回原值。这个参数在复制环境的从库上也要注意主库备份恢复后函数在从库上执行可能也会因为同样的校验失败影响复制线程。6.3 备份导出时报权限不足mysqldump需要的权限并不是SELECT就够的。如果指定了--single-transaction需要RELOAD权限来开启FLUSH TABLES WITH READ LOCK其实不需要它会用START TRANSACTIONRELOAD主要用于--master-data或--lock-tables相关操作。查看视图还要SHOW VIEW权限导出触发器需要TRIGGER权限。经验是给备份用户一次性授权全套GRANT SELECT, RELOAD, PROCESS, SHOW VIEW, TRIGGER, LOCK TABLES ON *.* TO backup_userlocalhost;授权后再执行备份基本不会因为权限问题报错。注意千万不要图省事用root跑定时备份脚本权限太宽密码一旦泄露整个数据库就裸奔了。6.4 备份内容乱码的排查思路先确认源库、目标库、mysqldump导出时三方的字符集设置是否一致。执行备份命令时可以显式加参数mysqldump -uroot -p --default-character-setutf8mb4 testdb testdb.sql恢复到目标环境时同样显式指定mysql -uroot -p --default-character-setutf8mb4 testdb testdb.sql如果在Windows上用编辑器查看sql文件注意文件编码也要选UTF-8否则看到的就是一屏乱码这并不是备份本身的问题。排查乱码时顺序应该是先看源库数据是否正确再看备份文件的字符集声明最后看恢复环境的客户端连接字符集。绕开这三个步骤直接猜浪费时间。6.5 备份过程拖垮了在线业务明明用了--single-transaction还是出现了锁等待或者性能下降首先要检查库里是否有长时间运行的事务。在可重复读隔离级别下如果事务开启后没提交后续DML可能会被阻塞。虽然mysqldump本身不锁表但如果它启动的快照读和已有的写事务同时存在备份过程中的临时表空间、undo log增长可能拖慢磁盘IO。另一个容易被忽略的点是mysqldump导大表期间sort buffer、net_buffer等相关内存区域占用上升如果服务器内存本来就紧张可能触发swap。所以大库备份尽量错峰或者给备份单独安排一台从库备份时连从库导出这是生产环境比较稳妥的解法。最后再分享一个我自己的习惯mysqldump备份文件在正式归档之前我至少会在本地测试环境做一次恢复演练。这个动作看起来多花了一些时间但真实故障发生时别人在会议室里开着应急会议你已经确认了备份文件可用这种确定性的踏实感值得投入。