ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL定时备份实战:crontab+mysqldump脚本方案详解

MySQL定时备份实战:crontab+mysqldump脚本方案详解 1. 备份需求分析与整体方案设计1.1 为什么需要定时备份先讲一个真实的教训我在日常运维中接过不少“数据库出问题”的求助印象最深的一次是这样的一位同事负责的内部系统数据库跑在测试服务器上平时改了表结构、清了数据都不在意觉得“反正数据也不重要”。结果某天有同事误执行了一条没有WHERE条件的UPDATE整张表几万条记录全被覆盖成了同一个值。当时所有人第一反应是找备份翻了一圈发现服务器上没有做过任何自动备份最后的完整备份还是两个月前手动导的。那个下午基本就是靠binlog一点点手工回放折腾了将近四个小时才勉强把数据恢复到接近误操作前的状态还有一部分更新的数据因为日志窗口覆盖已经找不回来了。这件事之后我把“定时自动备份”作为所有MySQL服务器的第一优先级基础项。不管你是跑生产业务的正式环境还是自己练手的虚拟机只要里面存了不想丢的东西就应该有一个按时按点、不需要人记着的备份机制。Linux下最经典、最轻量、也最可靠的组合就是crontab mysqldump一个负责调度一个负责导出再加一段简单的Shell脚本把它们串起来。这个方案不需要安装额外软件MySQL自带的mysqldump就是核心工具crontab是Linux系统自带的定时任务服务两条命令搭配起来就能实现每天凌晨自动备份、按日期归档、自动清理旧备份文件。下面是这个方案的整体架构触发层crontab定时任务按预设时间策略执行备份脚本执行层Shell备份脚本调用mysqldump导出数据并完成压缩、归档、日志记录存储层备份文件按日期命名存放定期清理覆盖旧文件验证层通过日志与手动恢复测试确认备份确实可用1.2 方案选型为什么默认选mysqldump而不是直接拷贝文件有人会问我直接把MySQL的数据目录比如/var/lib/mysql用cp或者rsync拷贝一份不也算备份吗算但不是好方案。数据目录是MySQL运行时正在读写的文件冷拷贝很容易得到一份不一致的数据。就算你先停了MySQL服务再拷贝生产环境不可能允许你每天定时停库备份。而mysqldump是在线备份工具专门处理这种情况它通过MySQL协议连接数据库以逻辑方式导出SQL语句集合。mysqldump的原理并不复杂可以这么理解它就是替你执行了一整套SELECT查询把表结构和数据逐行读出来整理成一条条CREATE TABLE和INSERT语句最终保存为一个后缀通常是.sql的文本文件。恢复的时候只要把这个SQL文件喂给mysql客户端它就会按顺序重新建表、把一行行数据插回去。数据库备份本质上就是“出来一套建表和插数据的剧本”恢复就是“把这个剧本重演一遍”。这种方式的好处有三个跨版本、跨平台恢复能力强SQL脚本本来就是文本拿到另一台机器上也能执行不需要和原来完全一致的MySQL版本、操作系统或硬件架构。粒度可控可以只备份某一个库也可以只备份某几张表甚至可以用--where参数只导满足条件的数据行灵活性非常高。在线执行不影响业务备份过程中数据库照常提供服务不需要停机。mysqldump唯一的短板是大数据量场景下性能偏慢。一个上百GB的库mysqldump全量导出可能要跑很久这种场景通常要换成物理备份工具比如Percona XtraBackup但那是另一个话题。对于大多数中小型系统mysqldump就是性价比最高的选择。另外要强调一个关键点很多人忽略备份不等于恢复。你辛辛苦苦导出了SQL文件如果从没验证过这个文件能不能顺利导入回MySQL那这个备份严格来说就不能算数。所以后面我会专门讲恢复测试的完整步骤。1.3 备份策略设计全量备份为主日志增量兜底一套合理的备份策略至少要想清楚几个问题多久备一次备份怎么留存丢了最近一段时间的数据怎么办先说频率。MySQL本身从安装到运行会持续产生binlog二进制日志记录所有写操作。这意味着就算你有每天凌晨的全量备份当天凌晨后到出问题那一刻之间的数据变化还是可以通过binlog找到。所以最理想的组合是每天凌晨crontab做一次全量备份 MySQL开启binlog记录增量变化。这样设计可以应对最常见的几类事故误删表、误更新数据用昨天凌晨的全量备份恢复基础数据再回放当天的binlog能把丢失的数据找回来。服务器硬盘损坏全量备份加binlog如果存储在另一块磁盘或另一台机器上依然能恢复。勒索病毒或恶意操作如果备份文件不在服务器本地而是定期同步到其他存储数据就不至于全丢。关于备份文件保留周期我一般的建议是本地至少保留7天条件允许的话保留14到30天。太短了可能上周的数据就已经找不回来了太长了磁盘空间会越占越多。保留多少天不是拍脑袋定的要结合备份文件的大小和磁盘剩余空间来匹配后面脚本里我会写一个按天数自动清理的策略。还需要明确备份的“存储位置”策略。最稳妥的做法是“本地异地”两份crontab先生成备份文件到本地目录再用另一个同步任务或者rsync、对象存储工具把备份文件传到另一台机器或云存储上。这样即使服务器整台挂掉备份数据也还在其他地方。这个扩展操作我会在最后一节提一下怎么接。2. 备份脚本详解与核心参数解读2.1 一个可以直接抄作业的备份脚本先放上我自己在用的备份脚本模板注释写在里面了照着改参数就能用#!/bin/bash # # MySQL 定时备份脚本 # 适用环境Linux MySQL 5.7 / 8.0 # 功能mysqldump全量备份gzip压缩按日期归档自动清理旧备份 # # 基础配置区按实际环境修改 MYSQL_HOST127.0.0.1 MYSQL_PORT3306 MYSQL_USERbackup_user MYSQL_PASSYourStrongPassword # 要备份的数据库空格分隔多个库名 DATABASESmydb1 mydb2 # 备份文件存放目录 BACKUP_DIR/data/mysql_backup # 备份保留天数 RETENTION_DAYS7 # 日志文件 LOG_FILE/var/log/mysql_backup.log # 日期变量用于备份文件命名 DATE$(date %Y%m%d_%H%M%S) # 记录日志函数 log() { echo $(date %Y-%m-%d %H:%M:%S) $1 $LOG_FILE } # 备份目录检测与创建 if [ ! -d $BACKUP_DIR ]; then mkdir -p $BACKUP_DIR log 创建备份目录 $BACKUP_DIR fi # 循环备份每个数据库 for db in $DATABASES; do # 备份文件名库名_日期.sql.gz BACKUP_FILE$BACKUP_DIR/${db}_${DATE}.sql.gz log 开始备份数据库: $db # 执行 mysqldump 并压缩 mysqldump -h$MYSQL_HOST -P$MYSQL_PORT -u$MYSQL_USER \ -p$MYSQL_PASS \ --single-transaction \ --quick \ --routines \ --triggers \ --events \ --set-gtid-purgedOFF \ $db 2$LOG_FILE | gzip $BACKUP_FILE # 判断备份是否成功备份文件不为空且大于0字节 if [ -s $BACKUP_FILE ]; then log 数据库 $db 备份成功文件: $BACKUP_FILE大小: $(du -h $BACKUP_FILE | cut -f1) else log 错误数据库 $db 备份失败备份文件为空 # 可以选择在这里发送报警邮件或企业微信通知 fi done # 清理超过保留天数的旧备份文件 find $BACKUP_DIR -name *.sql.gz -mtime $RETENTION_DAYS -type f -delete log 清理完成已删除超过 ${RETENTION_DAYS} 天的备份文件 # 输出日志分隔线方便查看每天的备份记录 echo ---------------------------------------- $LOG_FILE2.2 mysqldump参数逐个说明为什么用这些而不是那些脚本里每个参数都不是顺手写的都有明确的理由这里逐一拆解。--single-transaction是InnoDB引擎的“不锁表备份”神器。它的事务隔离机制使得mysqldump导出的数据是启动那一刻的一致性快照整个备份过程中其他会话照常读写备份出来的数据不会出现“前面导的是旧数据、后面导的是新数据”这种错乱。但是要注意一个前提它只能保证InnoDB表的一致性如果你的库里有MyISAM表备份过程中这些表仍然可能会被锁。如果数据库里还留着MyISAM表要么尽早改成InnoDB要么在备份脚本里加上--lock-tables参数确保一致性优先于并发性。--quick是让mysqldump逐行从服务器取数据写文件而不是把大量结果先缓存到内存里再一次性写出。不加这个参数当表数据量非常大时可能把客户端内存撑爆。加上后内存占用非常稳定这是大表备份的基本保护。--routines、--triggers、--events这三个参数容易漏但非常关键。它们分别导出存储过程/函数、触发器、事件调度器。很多人在恢复备份后发现业务报错查来查去最后发现是当初备份的时候没带这些参数数据库里的存储过程和触发器根本没导出来。默认情况下mysqldump是不导出这些对象的必须显式加上才备份。--set-gtid-purgedOFF是给MySQL 5.6以上启用了GTID的环境用的。如果不去设置它导出的SQL文件里会包含SET GLOBAL.GTID_PURGED这样的语句在导入到另一台库时经常因为GTID冲突报错。加上OFF之后备份文件里不带GTID信息导入恢复更省心。如果你用的还是MySQL 5.5这种没有GTID的版本这个参数不影响可以忽略。关于密码参数我在脚本里直接写在了命令行上。这是为了方便理解但严格来说这样有安全隐患系统的ps命令会暴露命令行里的密码。更安全的做法是把账号密码写进/etc/my.cnf的[mysqldump]配置段或者用mysql_config_editor工具保存加密的登录信息。生产环境建议至少把MySQL账号设置为专用备份账号只授予SELECT、LOCK TABLES、SHOW VIEW等必要权限不要直接用root。2.3 备份账号权限最小化授权的正确姿势备份账号不用给全权限只需要它能“把数据读出来”就行。建一个专用的backup账号是更规范的做法-- 创建备份专用账号仅授予必要的权限 CREATE USER backup_userlocalhost IDENTIFIED BY YourStrongPassword; -- 针对需要备份的库授予权限这里以 mydb1 为例 GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON mydb1.* TO backup_userlocalhost; -- 如果需要备份存储过程和函数还需要额外授予 GRANT SELECT ON mysql.proc TO backup_userlocalhost; -- 刷新权限 FLUSH PRIVILEGES;需要注意版本差异MySQL 8.0中mysql.proc已经不存在了存储过程的元数据存储在mysql.routines中但是一般不需要手动授权只需要给SHOW VIEW和SELECT权限就能正常导出。用专用账号还有个额外好处万一备份脚本被借用或泄露影响范围可控不至于一台数据库被连锅端。3. crontab定时任务配置与日志验证3.1 crontab基本语法比想象中简单备份脚本写好了剩下就是让Linux系统按时执行它。crontab的基本语法是六个字段表达方式像一个“表格”字段含义取值范围分钟每小时的第几分钟0-59小时每天的第几小时0-23日每月的第几天1-31月每年的第几个月1-12星期每周的第几天0-70和7都表示周日命令要执行的命令或脚本路径任意Shell命令每天凌晨2点30分执行一次备份对应的写法就是30 2 * * * /bin/bash /data/scripts/mysql_backup.sh其中*表示任意值都匹配。如果想周一到周五执行而周末不执行就把星期字段写成1-530 2 * * 1-5 /bin/bash /data/scripts/mysql_backup.sh如果只想在每周日凌晨执行一次完整备份适合业务量小的环境写30 2 * * 0 /bin/bash /data/scripts/mysql_backup.sh配置定时任务用crontab -e进入编辑器查看当前用户已有的任务用crontab -l想删除所有定时任务用crontab -r这个命令慎用不会二次确认执行就直接清空。3.2 设置crontab时最容易踩的三个坑第一坑脚本一定在crontab里完整路径。crontab执行时的环境变量和登录Shell里不一样PATH变量很精简经常找不到mysqldump命令。所以脚本内部要么写mysqldump的完整路径比如/usr/bin/mysqldump要么在脚本开头显式导出PATHexport PATH/usr/local/mysql/bin:/usr/bin:/bin:/usr/sbin:/sbin第二坑输出重定向。crontab任务如果输出到终端执行时会以邮件形式发送给本地用户这个邮件经常没人看还占磁盘空间。如果要调试最好把标准输出和错误输出重定向到日志文件比如30 2 * * * /bin/bash /data/scripts/mysql_backup.sh /var/log/mysql_backup_cron.log 21不过更推荐把日志管理放到脚本内部也就是我在脚本里写LOG_FILE的方式这样crontab行本身保持简洁日志格式也统一。第三坑判断服务是否在运行。有时crontab没执行先排查crond服务状态systemctl status crond如果服务没启动用systemctl start crond拉起来再执行systemctl enable crond设置开机自启。CentOS 6以前用的是service命令现在主流版本都是systemctl体系。3.3 查看crontab执行日志与结果验证配置完之后一定要确认备份真的执行了、真的成功了。有两种常见的验证方式。方式一查看系统cron日志。在CentOS/RHEL上crond的执行记录通常写进/var/log/cronUbuntu/Debian上则通常在/var/log/syslog里。查看方式grep mysql_backup /var/log/cron | tail -20能看到类似CMD (/bin/bash /data/scripts/mysql_backup.sh)的记录说明调度器确实在按计划触发了脚本。方式二查看备份脚本自己的日志。我前面脚本里设计的LOG_FILE路径是/var/log/mysql_backup.log每次执行结束会写入一行时间、数据库名、备份文件和文件大小。用tail -20 /var/log/mysql_backup.log快速看最近的备份情况。如果看到“备份失败文件为空”或者执行时间异常说明哪里出了问题。另外要特别提醒只看了日志还不够要学会检查备份文件本身。日志可能显示“成功”但备份文件有可能因为磁盘满等原因是一个损坏的压缩包。我一般会随手执行一条解压验证gzip -t /data/mysql_backup/mydb1_20250620_020001.sql.gzgzip -t只做完整性检查不解压内容。没问题会静默退出报错会明确提示。这个命令可以加进备份脚本末尾作为备份完成后的自动校验步骤。完整性的价值比“命令执行没报错”高得多真正到恢复的那一天一个能解压的完整备份文件才是有意义的东西。3.4 关于备份执行时间的选择策略备份时间不是随便定的要考虑业务低峰期和备份窗口。一般来说凌晨2点到4点是比较理想的备份窗口此时用户访问量少即使mysqldump对服务器有一定IO和CPU占用影响也更小。但有个点要注意如果数据库同时在做其他定时任务比如数据统计、报表生成、缓存刷新尽量错开时间避免多个任务挤在一起导致资源瓶颈。我遇到过生产环境凌晨2点同时跑批处理和备份结果磁盘IO打满备份跑了平时三倍时间的案例。如果业务是7×24小时全天高负载可以考虑把备份拆成多个薄备份比如周一三五备份数据库A周二四六备份数据库B分散IO压力。当然这要结合业务实际不是所有系统都需要这么复杂。4. 备份恢复演练与常见问题排查实录4.1 恢复流程不是每个备份都能叫“备份”前文提过备份和恢复是两件事这里具体演示一次完整的恢复流程。假设要恢复库mydb1到刚才备份的那个状态最直接的做法# 第一步解压备份文件 gunzip -c /data/mysql_backup/mydb1_20250620_020001.sql.gz /data/mysql_backup/mydb1_20250620_020001.sql # 第二步导入数据库 mysql -h127.0.0.1 -uroot -p mydb1 /data/mysql_backup/mydb1_20250620_020001.sql这里有一个重要前提目标数据库必须已经存在。如果mydb1库已经被误删了需要先创建空库再导入mysql -h127.0.0.1 -uroot -p -e CREATE DATABASE mydb1 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -h127.0.0.1 -uroot -p mydb1 /data/mysql_backup/mydb1_20250620_020001.sql导入过程中如果报错卡住常见原因是字符集不一致。日常建库时统一使用utf8mb4备份和恢复的字符集保持一致能省掉很多麻烦。也可以用mysql客户端里source命令实现同样的效果mysql -h127.0.0.1 -uroot -p mydb1 source /data/mysql_backup/mydb1_20250620_020001.sql;恢复完成后必须验证数据检查表的行数、抽查几条关键记录、确认存储过程存在。恢复之前可以先统计一下备份里各表行数恢复之后对比一下。这个验证动作虽然慢但必不可少。4.2 常见问题速查表备份过程中遇到的典型状况我在维护备份系统的过程中整理过一张实际问题排查表这里直接分享出来症状可能原因解决思路crontab到点没执行crond服务未启动查看并启动crond服务脚本手动执行成功crontab执行失败环境变量PATH缺失脚本内显式设置PATH或使用命令完整路径备份文件生成但大小为0mysqldump连接失败检查账号密码、网络连通性、MySQL是否正常运行备份文件解压报“unexpected end of file”磁盘空间不足导致写一半中断及时清理磁盘、扩大备份目录所在分区备份过程中业务查询变慢mysqldump占IO资源过高增加--single-transaction参数已加则检查锁表情况恢复导入时报GTID相关错误GTID环境未处理备份时加--set-gtid-purgedOFF参数备份内容缺少存储过程/触发器参数遗漏检查--routines/--triggers参数是否带上crontab日志出现“No MTA installed”相关提示任务输出无处投递在crontab行尾部重定向到日志文件4.3 几个我实际踩过的坑备份工作看起来简单但细节非常多。有几个问题我是真正遇到过并且费了不小的劲才解决的值得单独拿出来说。第一个是磁盘空间误判。以前我在一台服务器上把备份文件放在根分区/下面当时看df -h还剩下50多GB觉得够用。后来跑了几个月备份文件越攒越多某天执行gzip时发现文件写到一半磁盘满了压缩包损坏。而且因为残留的坏文件还占着空间连带当天的备份全失败。现在我的习惯是备份目录单独挂载一块磁盘比如/data不要和系统根目录混在一起再写一个磁盘剩余空间的判断逻辑如果剩余空间低于阈值比如10GB就自动报警并清理最旧的备份文件。第二个是crontab日志不显示执行记录。有次部署完crontab等了20多分钟看/var/log/cron什么记录都没有。排查了半天最后发现问题是crontab文件写错了。当时我写命令的时候没有注意换行整行内容里多了个奇怪的字符crontab解析失败直接不执行。排查方法很简单用crontab -l看下实际加载的内容明显能看到格式异常。之后我习惯用crontab -e编辑保存后立刻crontab -l确认一下。第三个是备份账号权限不足。刚开始图省事直接在脚本里用了业务账号做备份后来业务账号密码改了没人通知备份连续失败一周都没人发现。从那以后我才养成了每次做完备份配置后必须盯第一个执行周期的日志并且每周随机挑一天看备份文件的文件大小是否正常变化的习惯。自动化好是好但不能配置完就不管了备份系统的健康检查本身也需要“定时”坚持。4.4 备份任务的双重保障定期抽查与恢复演练最后分享一个我个人的习惯。除了crontab每天自动执行之外我一般还会设置一个手工提醒日历事件也行每个月做一次恢复演练。流程是随机挑一个没有业务影响的测试环境把当月的备份文件恢复上去然后用SQL语句统计关键表的行数和源库对比。这个动作看起来费时间但价值非常高它能提前发现很多“看似备份成功实则恢复不了”的隐患。有一次我就是这么发现的——某个备份文件虽然能正常解压但导入测试库后发现最新备份的表行数比前一天少了3000多行。当时查原因是备份脚本在循环备份多个库的时候两个库名顺序写错了其中一个库被重复备份另一个库根本没备份到。这类逻辑错误光看日志基本发现不了只有恢复对比才能暴露。再补充一个生产环境实用的小建议。备份脚本执行完成后可以顺手把生成的备份文件同步到另一台机器或者对象存储。一个简单的方法是直接用rsyncrsync -avz /data/mysql_backup/ backup-server-ip:/data/mysql_backup/也可以用云厂商的CLI工具传到对象存储。同步这份异地的目的很直白本地磁盘坏了备份还在异地服务器被加密勒索了备份还在异地。有了双层保障数据才算真正安全。5. 安全与性能优化补充5.1 备份文件加密的考虑备份文件本身是明文SQL包含全部数据内容如果备份目录被无关人员/程序访问到数据就泄露了。在涉及用户隐私、企业敏感数据的场景建议对备份文件做加密。简单的方法是用openssl# 加密gzip压缩后用AES-256-CBC加密输出.enc文件 gzip -c $BACKUP_FILE | openssl enc -aes-256-cbc -salt -pass pass:$ENCRYPT_PASS -out $BACKUP_FILE.enc # 解密恢复时先解密再解压 openssl enc -d -aes-256-cbc -pass pass:$ENCRYPT_PASS -in $BACKUP_FILE.enc | gunzip $BACKUP_FILE.sql解密密码要另存不要和备份文件放同一台机器。这种加密操作会稍微增加CPU占用对于午夜低峰期的备份任务来说完全可接受。5.2 大数据量场景的备份策略调整如果单个数据库超过50GB每天全量mysqldump的耗时就会变得很可观。此时可以考虑调整策略降低全量备份频率每周一次全量备份比如周日凌晨其余每天只备份binlog增量。表级拆分备份大表单独备份小表合并备份缩小单次备份文件体量。备份时间窗口拉长允许备份在低峰期跨多个时间段完成配合告警而不是一刀切限制时长。binlog增量备份的做法是先在全量备份前用FLUSH LOGS切换当前binlog记录切换后的binlog文件名和起始位置然后把binlog文件连同索引定期打包。这样恢复时先恢复最近一次全量备份再按顺序回放从记录位置开始的所有binlog。这个方法展开讲又是一大篇但思路先交代清楚后续遇到实际需求时可以针对性地深挖。5.3 备份性能测试与资源监控备份任务上线之前建议先手动执行一遍脚本观察执行耗时、磁盘IO、CPU占用。如果备份时数据库的CPU占用率上涨明显还可以考虑给mysqldump加--compress参数减小网络传输量但这个参数在本地备份时意义不大。备份执行期间可以配合iostat -x 1、top观察系统负载确认没有明显的性能热点。资源监控是很多人忽略的一块。定时任务稳定运行之后可以做一个月度巡检检查备份日志有没有报错、备份文件大小有没有突变突然暴增或暴减都可能是业务异常或备份逻辑问题、磁盘空间使用率是否健康。把这些巡检项做成一个简单的清单每次花十几分钟就能过一遍长期下来能避免很多临时抱佛脚的情况。我在实际运维中越来越体会到数据库备份这件事技术含量不在于“会不会用mysqldump写出SQL文件”而在于怎么把它做成一个稳定、可靠、可验证、长期不出问题的自动化体系。crontab加个脚本只是起点真正核心的是参数有没有选对、权限有没有最小化、日志有没有监控、恢复有没有演练、异地有没有留存。把这几件事都补齐了面对“数据库出问题了”这句话的时候心里才真正有底。
返回列表