Windows MySQL自动备份bat脚本:定时备份与30天清理实践
Windows服务器上跑MySQL最让我头疼的从来不是SQL写不好而是备份这件事。装个图形工具固然省心可一旦机器没装桌面环境、或者半夜两点数据库被搞挂了能救命的往往还是那条不声不响的bat批处理脚本。这篇我把自己一直在用的自动备份脚本完整放出来核心就两件事每天定时把指定库通过mysqldump导成SQL文件再顺手清理掉30天前的旧备份全部由Windows任务计划程序调度不依赖任何第三方工具。如果你是运维、兼职管服务器的开发或者自己买了Windows云主机跑了MySQL这份脚本直接改几行配置就能上岗。1. 为什么Windows上的MySQL备份绕不开bat批处理脚本1.1 这个脚本解决的是哪类麻烦先说清楚适用场景。中小公司的Windows Server上跑着MySQL多半是内部OA、ERP、测试环境、个人网站后端这类负载不算高的库。这类环境往往没有预算上专业的备份软件DBA岗位也不存在备份这件事要么靠人肉每周导一次要么干脆没人管。还有一个典型场景开发机或测试服务器上的MySQL数据丢了虽然不致命但重建库表、补录测试数据非常浪费时间。这个bat脚本就是用来填这个坑的。它做的是逻辑备份用MySQL自带的mysqldump把指定数据库导出成一个完整的SQL文本文件。这个文件拿到任何一台装了MySQL的机器上都能恢复跨平台、跨版本都很方便。它不解决增量备份、不解决实时灾备但能保证最坏情况下我能找回30天内任意一天的完整数据这对绝大多数中小业务来说已经够了。如果MySQL不在本机、在另一台Linux服务器上这个脚本同样能用。只要本机能连通目标MySQL的3306端口、本机装了mysqldump.exe远程库也能按时备份到Windows磁盘上。我实际就是这么干的——Linux上的业务库Windows机器定时拉备份统一收拢到一个目录管理起来很省事。1.2 为什么不是GUI工具也不是PowerShell可能有人问用Navicat或者MySQL Workbench点几下不是更简单确实简单但没法自动化。你半夜两点不会爬起来点导出服务器也不会因为今天是周五就自动备份。所有GUI工具都解决不了无人值守定时执行这个核心诉求要么依赖任务计划程序去调用它的命令行接口兜兜转转又回到脚本。PowerShell相比bat语法更现代、逻辑更强但对国内大多数半路出家的运维来说维护成本偏高。老旧的Windows Server 2008 R2、Win7上PowerShell版本参差不齐还有一部分机器的执行策略默认禁用脚本。bat的好处是从Windows 95到Windows 11通吃任务计划程序直接指向它就能跑出了问题打开看一眼就能排查对能用就行的场景来说没有比它更合适的了。1.3 整体设计备份、校验、清理三步走这个脚本的核心逻辑可以拆成三个动作备份mysqldump导出指定库到备份目录文件名带精确到秒的时间戳。校验通过mysqldump的退出码判断备份是否成功成功才把文件大小写进日志用于人工确认。清理用forfiles删除30天前的旧SQL文件只保留最近30天的备份。这里有一个很容易被忽略的设计原则备份失败时必须跳过清理步骤。如果某天mysqldump因为密码过期、磁盘满等原因失败了而清理逻辑还正常跑就会把之前的好备份一并删掉那就真的什么都没了。脚本里我让备份失败时直接exit /b 1不让它继续往下走这是底线。2. 备份脚本完整拆解从时间戳到forfiles清理2.1 完整脚本可直接复制保存下面是完整的脚本保存为mysql_backup.bat就能用。注意保存编码选ANSI否则中文注释在cmd里会显示成乱码虽然不影响逻辑但排查问题的时候很痛苦。echo off setlocal enabledelayedexpansion title MySQL Auto Backup :: :: MySQL 自动备份 30天清理脚本 :: 基于 mysqldump forfiles不依赖第三方工具 :: 保存此文件为 ANSI 编码避免中文注释乱码 :: :: ---------- 配置区按实际情况修改 ---------- set DB_HOST127.0.0.1 set DB_PORT3306 set DB_USERroot set DB_PASS你的数据库密码 set DB_NAME你的库名 set MYSQL_DUMPC:\Program Files\MySQL\MySQL Server 8.0\bin\mysqldump.exe set BACKUP_DIRD:\MySQLBackup set KEEP_DAYS30 set LOG_FILE%BACKUP_DIR%\backup.log :: ---------- 自动创建备份目录 ---------- if not exist %BACKUP_DIR% mkdir %BACKUP_DIR% :: ---------- 生成固定格式时间戳 ---------- for /f tokens2 delims %%i in (wmic OS Get LocalDateTime /value ^| find ) do set DT%%i if not defined DT ( for /f usebackq delims %%j in (powershell -NoProfile -Command Get-Date -Format yyyyMMdd_HHmmss) do set TS%%j ) else ( set TS!DT:~0,8!_!DT:~8,6! ) echo %LOG_FILE% echo [%date% %time%] Backup start: %DB_NAME% %LOG_FILE% set BACKUP_FILE%BACKUP_DIR%\%DB_NAME%_%TS%.sql :: ---------- 执行 mysqldump 备份 ---------- %MYSQL_DUMP% -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% --single-transaction --routines --triggers --events --default-character-setutf8mb4 %DB_NAME% %BACKUP_FILE% 2 %LOG_FILE% if errorlevel 1 ( echo [%date% %time%] ERROR: mysqldump exit code !errorlevel! %LOG_FILE% echo [%date% %time%] ERROR: Backup FAILED, cleanup skipped. %LOG_FILE% exit /b 1 ) for %%A in (%BACKUP_FILE%) do set FILE_SIZE%%~zA echo [%date% %time%] SUCCESS: %BACKUP_FILE% size%FILE_SIZE% bytes %LOG_FILE% :: ---------- 清理超过 KEEP_DAYS 天的旧备份 ---------- echo [%date% %time%] Cleanup start: older than %KEEP_DAYS% days %LOG_FILE% forfiles /p %BACKUP_DIR% /m *.sql /d -%KEEP_DAYS% /c cmd /c del /q path %LOG_FILE% 21 echo [%date% %time%] Cleanup finished %LOG_FILE% echo [%date% %time%] All done %LOG_FILE% exit /b 02.2 配置区改这六处就能跑脚本开头就是配置区我用分隔线标出来了。最需要留意的是MYSQL_DUMP这个路径。MySQL 8.0 默认装在C:\Program Files\MySQL\MySQL Server 8.0\bin5.7则是MySQL Server 5.7。如果你安装时改了目录或者机器上只装了MySQL客户端组件路径要跟着改。如果系统提示找不到mysqldump先用资源管理器去确认一下bin目录里到底有没有这个exe。DB_HOST默认是127.0.0.1。备本机库就保持这个值备远程库改成目标服务器IP。这里有个细节如果MySQL账号只授权了localhost访问而你连的是127.0.0.1在MySQL的用户表里这俩其实是两个概念容易踩坑。保险起见本机备份时账号的Host用localhost远程备份时单独建一个Host允许远程访问的账号。备份账号不建议直接用root。我在生产环境习惯单独建一个专用备份账号CREATE USER backuplocalhost IDENTIFIED BY Backup2024; GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES, PROCESS, RELOAD ON *.* TO backuplocalhost; FLUSH PRIVILEGES;这样即使备份脚本所在机器被攻破这个账号也只能导出数据不能改数据。权限看着多但mysqldump在不同场景下确实需要这些权限比如--single-transaction需要RELOAD或至少能开启事务快照备份触发器需要TRIGGER权限。2.3 固定格式时间戳绕开 %date% 的坑很多初版备份脚本会直接拿%date%拼文件名这是个大坑。Windows的%date%输出完全取决于系统区域设置有的机器输出2024/03/15 周五有的输出03/15/2024还有的带斜杠、带空格。你用%date:~0,10%这种截取方式换一台机器就全废了文件名里可能出现冒号和斜杠直接导致创建文件失败。我的方案是用wmic OS Get LocalDateTime获取固定格式的时间。它的输出长这样LocalDateTime20240315023000.000000480取前8位20240315是日期第9到14位023000是时分秒拼起来就是20240315_023000既适合排序又不会重复。脚本里用了延迟变量展开!DT:~0,8!因为DT是在同一个括号代码块里赋值的普通%DT%取不到。这也是为什么文件开头要setlocal enabledelayedexpansion。需要说明的是Windows 11 24H2开始微软已经默认移除wmic了所以脚本补了一个PowerShell fallback。如果wmic不可用DT变量不会被赋值就走PowerShellGet-Date的备份方案。两条路至少有一条能通。2.4 mysqldump 参数逐个说清楚核心备份命令是这一行参数逐个看%MYSQL_DUMP% -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% --single-transaction --routines --triggers --events --default-character-setutf8mb4 %DB_NAME% %BACKUP_FILE% 2 %LOG_FILE%-h主机、-P端口、-u用户、-p密码。注意-P是大写小写-p是密码这个拼错太常见了。--single-transaction是关键参数。它让 mysqldump 在 InnoDB 引擎上利用事务一致性快照做备份备份过程中不会长时间锁表线上业务可以继续读写。但它对 MyISAM 表无效如果你的库里还有MyISAM表备份时这些表会被锁住所以备份尽量安排在低峰期跑。--routines --triggers --events显式导出存储过程、函数、触发器和事件计划。MySQL 5.x 默认不导出这些很多人备份完才发现丢了存储过程恢复的时候业务直接报错。8.x虽然默认行为有变化但显式写出来在任何版本下都不会错。--default-character-setutf8mb4防止导出SQL里的中文乱码。如果你的库本身就是utf8mb4这行尤其重要。再看重定向。 %BACKUP_FILE%把mysqldump正常输出的SQL写进备份文件2 %LOG_FILE%把标准错误追加到日志。这里有个容易翻车的习惯很多人会写成 file 21把错误信息和SQL混在一起。mysqldump运行时的警告比如命令行密码不安全提示、表不存在提示一旦混进SQL文件恢复时MySQL会把它当成无效SQL报错整个备份就废了。所以stderr和stdout必须分开。命令行直接带密码每次备份日志里都会出现一条Using a password on the command line interface can be insecure.的警告这是正常的不是脚本错误。想彻底消除它见第5.2节。2.5 forfiles 的30天清理逻辑清理用系统自带的forfiles一行搞定forfiles /p %BACKUP_DIR% /m *.sql /d -%KEEP_DAYS% /c cmd /c del /q path/p指定搜索目录。/m *.sql只匹配SQL文件不会误删目录里其他东西。/d -30表示匹配修改日期早于或等于30天前的文件。也就是说今天8月15日跑脚本7月15日及更早的备份会被删掉最近30天的保留。如果你希望至少保留完整的30个备份文件而不是按自然日算那得换dir /b /o-d加循环统计但日常场景按天清理已经够用了。/c cmd /c del /q path对每个匹配到的文件执行删除。注意path由forfiles自动替换成完整路径不要在它外面再加引号forfiles默认会给含空格的路径补引号你再加就容易出成对的引号打架。forfiles有个小噪音如果没有匹配到旧文件它会向stderr输出一句ERROR: No files found with the specified search criteria.。这不是真出错脚本里已经把它追加到日志里了第一次部署的人看到别慌下次有30天前的旧文件时它就不会出现。3. 部署到任务计划程序让备份每天自动跑3.1 图形界面创建任务的完整路径脚本写得再好不挂到任务计划程序上就只是手工工具。WinR输入taskschd.msc打开任务计划程序点右侧创建任务主要配置如下常规名称填MySQLAutoBackup勾选不管用户是否登录都要运行再勾选使用最高权限运行。下面配置下拉框选Windows 7 / Windows Server 2008 R2或更高版本兼容性最稳。触发器点新建选按预定计划开始时间设成02:00勾选每天。如果你的服务器凌晨有业务批处理跑注意错开时间。还可以勾上如果错过计划的启动时间则立即启动任务这样服务器如果刚好在2点重启开机后任务会自动补跑不至于漏备份。操作点新建操作选启动程序程序或脚本填D:\scripts\mysql_backup.bat。这里有个细节——起始于一定要填bat所在目录比如D:\scripts。虽然我们的脚本内部全部用了绝对路径不填也能跑但养成填的习惯以后脚本里如果加了相对路径的引用不会翻车。条件如果你的机器不是插电就休眠的笔记本把只有在计算机使用交流电源时才启动此任务这一项取消勾选。很多服务器实际上是台式机或工作站不取消的话一旦UPS供电或者电源策略微妙变化任务可能不执行。设置建议把如果任务失败请按此频率重新启动改成5分钟后重启最多3次。这样数据库瞬时抖动导致的备份失败会自动重试几轮。3.2 命令行一条命令创建任务不想点GUI用schtasks也能建schtasks /Create /TN MySQLAutoBackup /TR D:\scripts\mysql_backup.bat /SC DAILY /ST 02:00 /RU SYSTEM /RL HIGHEST /F/RU SYSTEM表示以系统账户运行这样不需要输入用户密码也天然隐藏了黑窗口。命令行方式适合批量部署多台服务器写个for循环就能把同样的任务推到好几台机器上。要注意的是SYSTEM账户访问本地磁盘没问题但如果备份目录是NAS共享路径SYSTEM访问共享目录容易遇到认证问题那就不如GUI里指定一个有权限的账号。3.3 运行身份与权限最容易翻车的地方我帮人排查计划任务不跑的案例里十有八九是身份和权限问题。第一种坑任务以某个普通用户身份创建而这个用户的密码设置了过期策略。Windows任务计划在不管用户是否登录都要运行模式下会把这个用户的密码加密存储一旦密码到期或用户改了密码任务立刻罢工。解决办法是给任务指定一个长期有效的服务账号或者用SYSTEM账户。第二种坑备份目录没有写权限。如果任务用SYSTEM跑写D盘没问题如果用普通用户跑而备份目录是管理员创建的普通用户可能没有写权限mysqldump会直接报权限不足。确认方法很简单命令行模式下手动运行一次bat能跑通再挂计划任务。第三种坑黑窗口。如果你在任务的常规里没有勾不管用户是否登录都要运行而是选了只在用户登录时运行那么每天2点会弹出一个cmd窗口一闪而过还算好碰上卡住的备份任务窗口可能一直挂着。勾上不管用户是否登录不仅能解决问题还不需要用户登录就能执行这才是无人值守的正确姿势。4. 备份不等于安全验证与故障排查4.1 两分钟读日志确认备份真的成功备份脚本写完不能就这么不管了第二天早上花两分钟看一眼日志D:\MySQLBackup\backup.log正常情况下末尾能看到这样的记录[2024/08/15 02:00:01] SUCCESS: D:\MySQLBackup\yourdb_20240815_020001.sql size152985600 bytes [2024/08/15 02:00:03] Cleanup finished [2024/08/15 02:00:03] All done重点看SUCCESS行里记录的文件大小。如果库平时有1GB数据备份文件突然变成几十KB甚至0字节那基本可以判断备份内容不完整只是mysqldump进程没有报错退出而已。文件大小是判断备份是否异常的最快指标我甚至建议把日志路径直接放到一个每天会瞟一眼的地方比如桌面快捷方式或一个共享盘目录。4.2 用 Dump completed 快速判断文件完整性只看退出码还不够mysqldump正常完成的SQL文件末尾有一段固定的注释-- Dump completed on 2024-08-15 2:00:01可以直接用findstr验证findstr Dump completed D:\MySQLBackup\yourdb_20240815_020001.sql有输出就说明mysqldump执行到了最后一行文件不是半路中断的。把它加进日常排查脚本里或者直接在命令行跑一下确认比人眼打开几万行的SQL文件靠谱得多。注意这个文件如果已经备份到远程验证操作要在本地没删除前做别等30天清理后才发现上个月备份全是坏的。4.3 恢复演练备份能不能用试过才知道这是整个备份方案里最重要、也最容易被跳过的一步。备份文件没恢复过就不能算备份成功。我第一次部署这套脚本时第二周就把新备份恢复到一个临时库里做校验结果发现mysqldump导出的SQL里有几条因为字符集问题恢复报错当场堵住了隐患。恢复演练流程很简单。先建一个临时空库mysql -uroot -p -e CREATE DATABASE test_restore CHARACTER SET utf8mb4;再导入备份文件mysql -uroot -p test_restore D:\MySQLBackup\yourdb_20240815_020001.sql导入完成后对比关键表的行数mysql -uroot -p -e SELECT COUNT(*) FROM test_restore.orders; mysql -uroot -p -e SELECT COUNT(*) FROM yourdb.orders;两边数字一致基本就放心了。我个人的习惯是每季度干一次这事顺便计时看看恢复速度心里对RTO有个数。4.4 常见故障排查表现象大概率原因处理办法ERROR 2003 Cant connectMySQL未启动、端口不对、防火墙拦截检查服务状态telnet 127.0.0.1 3306测试端口Access denied for user账号密码错、Host限制、密码含特殊字符确认授权Host与DB_HOST匹配用默认配置文件方案避免特殊字符mysqldump 不是内部或外部命令系统PATH里没有或MYSQL_DUMP路径不对确认bin目录实际路径安装MySQL Client组件计划任务上次结果 0x2任务里程序路径或起始目录写错检查任务操作里的路径和引号计划任务上次结果 0x1脚本自身失败退出码非0打开backup.log看最后的ERROR行备份文件0字节stderr被吞、认证失败、磁盘满看日志里的mysqldump警告确认磁盘可用空间日志中文乱码bat文件保存编码不是ANSI记事本另存为ANSI编码重新保存forfiles报No files found没有可清理的旧文件正常提示忽略即可5. 从能用走到好用进阶优化方向5.1 备份文件压缩磁盘不够时的选择SQL文本文件的压缩率很可观同一份备份zip或7z压完通常只有原来的20%-40%。如果磁盘空间紧张备份完成后顺手压缩再删除原始SQL能成倍延长保留周期。如果你的机器装了7-Zip脚本里加两行C:\Program Files\7-Zip\7z.exe a -tzip %BACKUP_FILE%.zip %BACKUP_FILE% -mx5 del /q %BACKUP_FILE%注意压缩后文件后缀变成了.zipforfiles的匹配模式要从*.sql改成*.zip否则清理逻辑永远删不到压缩包。另外压缩命令本身也可能失败建议压缩完再检查一下zip文件是否存在再删原文件别压缩失败还把原始备份删了。如果不想额外装7-Zip也可以用Windows自带的PowerShellCompress-Archive但处理大文件时速度和稳定性都不如7z我一般只在临时环境里用它。5.2 告别明文密码defaults-extra-filebat文件里明文写密码总有安全顾虑尤其是脚本放在共享目录或者多人可访问的服务器上。更稳妥的做法是建一个独立的配置文件让mysqldump从文件里读密码命令行不再出现任何敏感信息。新建一个文本文件比如C:\secure\mysql_bkp.cnf内容[client] host127.0.0.1 userbackup passwordBackup2024然后备份命令改成%MYSQL_DUMP% --defaults-extra-fileC:\secure\mysql_bkp.cnf --single-transaction --routines --triggers --events --default-character-setutf8mb4 %DB_NAME% %BACKUP_FILE% 2 %LOG_FILE%这样命令行里不再有-h/-u/-p日志里的密码警告也消失了。接下来用icacls锁一下配置文件权限只允许当前管理员账户读取icacls C:\secure\mysql_bkp.cnf /inheritance:r /grant:r %USERNAME%:R注意如果计划任务用的是SYSTEM身份运行SYSTEM账户也要给读取权限。这一步别漏权限设太死反而会导致任务读取配置失败。5.3 多库备份与异地容灾如果一台机器上有多个业务库不想一个个配多个任务可以在配置区加一个库列表用for循环逐个导出set DB_NAMESdb1 db2 db3 for %%D in (%DB_NAMES%) do ( %MYSQL_DUMP% ... %%D %BACKUP_DIR%\%%D_%TS%.sql 2 %LOG_FILE% )或者干脆用mysqldump自带的--databases参数一次把所有指定库导成一个文件%MYSQL_DUMP% ... --databases db1 db2 db3 all_%TS%.sql这两种方式按需选择分开导的好处是恢复单个库更快合在一起导的好处是备份文件数量少、管理简单。还有一个不能忽视的点备份文件留在本机磁盘上万一磁盘坏了备份跟着一起没。所以在备份完成后加一个robocopy同步到NAS或另一台服务器是非常值得的投资robocopy D:\MySQLBackup \\NAS\backup\mysql *.sql /MOV /R:2 /W:5这里有个robocopy特有的坑它的退出码0到7都算成功只有大于等于8才算失败。直接在bat里if errorlevel 1判断会误报正确写法是if %errorlevel% geq 8 ( echo [%date% %time%] ERROR: robocopy failed, code!errorlevel! %LOG_FILE% )如果你也刚好被Windows上的MySQL备份折腾过希望这份脚本能帮你少走弯路。我至今仍保留一个习惯每月手点一次恢复演练毕竟备份存在的意义不是文件堆在那儿而是真到要恢复的那一刻能拿得出来。