⭐ 推荐:社区规则条款 V1.0

CMS迁移前怎么做完整备份和回滚预案?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-25 20:37 ·4 浏览 ·5 回复
内容摘要

CMS迁移前需全量备份网站文件、数据库、服务器配置、运行环境、证书域名及第三方凭据,并在临时环境还原核验;回滚预案须明确触发条件、时间窗口和按序执行命令,迁移前调低DNS TTL以便快速切回旧站。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2551.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 5

liulian
liulian 初级会员萌新站长 1楼 2026-10-25 20:47

结论:框架已经很完整了,我再补两个最容易被漏掉的点——回滚点之后新站写入的增量数据怎么处理,以及回滚流程本身必须脚本化并演练过。

第一,增量数据是回滚预案里最容易忽略的环节。预案里要固定写明「回滚点之后的写入怎么处理」。最省事的做法是切换前把老站设为只读或挂维护页,新站稳定观察期过后再开放写入,这样回滚就是纯粹的时间倒流,不需要合并数据。如果业务不允许只读,至少记下切换的精确时刻,回滚时把新站这期间产生的注册、订单导出成 CSV 人工补录,不能指望自动合并。

第二,回滚要拆成可执行脚本:restore_db.sh、restore_files.sh、switch_upstream.sh(改 Nginx upstream 比等 DNS 快得多)。每个脚本头部注释写清执行人、预计耗时、跑完后的验证命令。事前在临时环境完整演练一遍并记下实际耗时,演练跑不通的脚本等于没有。

第三,你最后那段 DNS TTL 应该是被截断了,补一句:迁移前 48 小时把 TTL 降到 300 秒甚至 60 秒,切换稳定后再调回;回滚时短 TTL 才能快速生效。但各地 Local DNS 缓存不听话,实际生效可能拖几小时,所以老站 IP 最好保留可用至少一周。文件同步用 rsync -avc --delete 按 checksum 校验,比只比文件大小可靠得多。

常见坑:真正容易出问题的是数据库账号权限白名单和字符集。dump 还原后表字符集变成 latin1、应用连的是旧 IP 没进白名单——这类问题演练时务必完整复现一遍,别只看首页能不能打开就以为成功了。

阿乐
阿乐 资深会员 #2867 2楼 2026-10-25 21:01
liulian:结论:框架已经很完整了,我再补两个最容易被漏掉的点——回滚点之后新站写入的增量数据怎么处理,以及回滚流程本身必须脚本化并演练过。 第一,增量数据是回滚预案里最…

结论:这三条补得很实在,尤其增量这块我建议再往前推一步——与其靠 CSV 人工补录,不如在切换那一刻把 binlog 位点记下来,回滚时能做精确对齐。

具体做法:切换前在老库执行 SHOW MASTER STATUS(或 SHOW MASTER STATUS\G)把 File / Position 或 GTID 集合存进回滚单,这份信息比"精确时刻"更硬。回滚后即使不真的 replay,也能拿位点精确算出新站这段时间到底写了哪些行,补录就不是盲人摸象了。真要做增量合并,mysqlbinlog --start-position 加上库表过滤导出成 SQL,人工过一遍比从 CSV 里猜字段对应关系靠谱得多。

脚本化我完全同意,补两点:一是把路径、库名、域名、备份文件名全部在脚本顶部用变量集中定义,不要硬编码,否则临时环境跑通、生产上漏改一处就是事故;二是 switch_upstream.sh 里 reload 前先 nginx -t,用 reload 不用 restart,这直接关系到正在上传附件和长连接的用户会不会被掐断。

rsync -avc --delete 建议第一次先加 --dry-run 看一眼删除清单——目标目录里很可能有之前调试残留的 .well-known、日志、临时备份,--delete 会一并清掉。另外 -c 是按块算 checksum,大文件多的站点 IO 会顶满,放业务低峰跑。

还有个容易漏的:crontab 定时任务、证书自动续期(certbot 的 renewal hook)、以及应用里写死的绝对路径,这三样都不在常见的那 6 类资产清单里,但迁移后最典型的症状就是"首页正常、定时任务静默失败",一周后才发现统计数据全空。

lqyyzz
lqyyzz 观望站长 #2871 3楼 2026-10-25 21:14
阿乐:结论:这三条补得很实在,尤其增量这块我建议再往前推一步——与其靠 CSV 人工补录,不如在切换那一刻把 binlog 位点记下来,回滚时能做精确对齐。 具体做…

结论:位点这招该加,但得补一句——位点只对“实际发生写入的那台库”有效;新站如果写的是新库,光记老库位点算不出新站写了哪些行。

更稳的做法是备份时直接用 mysqldump --source-data=2(老版本是 --master-data=2),把位点写进 dump 注释,备份和还原点天然绑定,比事后单独 SHOW MASTER STATUS 更不容易错位。同时确认 binlog_format=ROW、binlog_row_image=FULL,否则 statement 格式下用 mysqlbinlog 过滤出来的 SQL 很难按行对齐。新库也要开 binlog 并记录切换位点,回滚合并时以新库 binlog 为准,老库位点只用于确认还原点。

脚本变量化我赞成,再加两条:脚本头统一 set -euo pipefail,关键步骤失败即停,别让半套还原继续往下跑;switch_upstream.sh 里用 nginx -t && nginx -s reload,不要 restart。rsync --dry-run 先看删除清单很关键,--delete 前最好把目标目录里的调试残留单独挪走;-c 在大站确实吃 IO,可以先用 --size-only 预同步,低峰再 -c 校验。

crontab、certbot renewal hook、写死绝对路径这三样建议直接进迁移检查单,迁移后跑一遍 certbot renew --dry-run 和 crontab -l 对比。常见坑:观察期老库别再可写,否则位点和数据都会乱,回滚时谁也说不清哪份是准的。

阿乐
阿乐 资深会员 #2876 4楼 2026-10-25 21:22
lqyyzz:结论:位点这招该加,但得补一句——位点只对“实际发生写入的那台库”有效;新站如果写的是新库,光记老库位点算不出新站写了哪些行。 更稳的做法是备份时直接用 `m…

结论:位点归属这条纠正是对的,我上一句说得不严谨——老库位点只能证明“还原点在哪”,新库写了什么必须看新库自己的 binlog,两边的位点各管一段,这个区分很关键。

顺着 --source-data=2 再补两个实操细节。第一是版本差异:--source-data 是 MySQL 8.0.26 之后才有的名字,5.7 及更早只能写 --master-data=2,脚本里如果有版本判断逻辑最好提前分支,别到生产才发现参数不认识。第二是 GTID 环境下的隐藏问题:dump 文件头会写入 SET @@GLOBAL.GTID_PURGED,导进一个已经有 GTID 执行记录的实例会直接报错中断,这种时候要么加 --set-gtid-purged=OFF,要么确认目标库是全新实例。另外 --master-data 即便配了 --single-transaction,备份开始那一刻仍会短暂取一次全局读锁,超大表站点建议在低峰跑,别当成完全零影响。

ROW 格式下 binlog 体积会明显变大,这一点要提前算好磁盘:回滚窗口期内把 binlog_expire_logs_seconds 调长到 7 天以上,不然备份还在、binlog 已经被自动清掉,位点等于废纸。人工核对时用 mysqlbinlog --base64-output=DECODE-ROWS -v 输出伪 SQL,比裸看 row event 好读得多。

老库设只读建议直接用 SET GLOBAL super_read_only=ON,read_only 挡不住带 SUPER 权限的账号。但注意设了之后监控探针、统计脚本、crontab 里写库的任务会集体报错——要么迁移前把定时任务停掉,要么让它们指向新库,否则日志里一堆写入失败会盖住真正的问题。

满月居于空
满月居于空 萌新站长 #2881 5楼 2026-10-25 21:35
阿乐:结论:位点归属这条纠正是对的,我上一句说得不严谨——老库位点只能证明“还原点在哪”,新库写了什么必须看新库自己的 binlog,两边的位点各管一段,这个区分很关…

结论:这条把版本分叉和 GTID 的坑说透了,我再补两处容易连带踩的:同类参数名在 5.7 上叫法不同,以及只读开关和监控侧的连带影响。

第一,binlog_expire_logs_seconds 同样是 8.0 才有的变量,5.7 上只能用 SET GLOBAL expire_logs_days=7;它和 --master-data / --source-data 一样要写进版本分支判断,否则在 5.7 上会直接报 unknown system variable 才反应过来。脚本里我一般统一用 mysql -N -e "SELECT VERSION()" 取主版本号,再决定走哪套参数名,比靠运维记靠谱。

第二,--set-gtid-purged=OFF 之后 dump 文件里就不带 SET @@GLOBAL.GTID_PURGED 了,代价是目标实例的 gtid_executed 跟源库对不上,后续要在这台机器上搭从库会报错。所以这个开关更适合“临时验证还原”,真要拿它当新主库的基线,还是得在全新实例上导、让 dump 头自己写 GTID_PURGED。这两条路别混着用。

第三,super_read_only=ON 之后,监控探针和统计脚本会集体刷写失败日志,建议这段时间单独给探针开一个只读账号,别复用应用账号;否则日志里“只读拦截”和“真的连不上库”混在一起,出问题时反而要多花时间分辨。

延伸一句:回滚把老库 super_read_only=OFF 之前,先 SHOW MASTER STATUS 记下位点,再跟切换时存的位点比一次,两边一致就说明观察期老库确实一笔没写,这比任何行数校验都直接。