rsync 迁移文件全拷完了,MySQL 却起不来:五个“隐蔽默认值”让数据目录搬家变成翻车现场

开场:不是文件没拷过去,而是 rsync 太“聪明”

前阵子帮朋友迁一台老业务机,新机器都用轻云互联开好了,rsync -av --delete 整个 /data 目录同步过去,文件数量、权限看着都正常。结果 MySQL 直接起不来,PHP 站点全部 502。排查到半夜,问题全都出在 rsync 的几个“默认行为”上——文件固然全过去了,但文件附带的东西丢了一堆,还夹带了不该过去的垃圾。

一、-a 不等于 cp -a:uid、xattr、硬链接全被默认丢掉

很多人以为 rsync -a 等于 cp -a,实际上 -a(归档)只包含 -rlptgoD不包含 -H(硬链接)、-A(ACL)、-X(xattr)

# 看似完整,实则丢了 ACL/XATTR/硬链接
rsync -av --delete /data/mysql/ root@10.0.0.2:/data/mysql/

# 备份迁移的正确姿势
rsync -avHAXA --numeric-ids --delete /data/mysql/ root@10.0.0.2:/data/mysql/

丢了 xattr 在 CentOS/RHEL 系尤其致命:SELinux 上下文(如 system_u:object_r:mysqld_db_t:s0)没传过去,MySQL 启动后写表会被 SELinux 拦截,日志里只有 /var/log/audit/audit.log 的一堆 AVC denied。验证方法:

getfattr -d -m - /data/mysql/ibdata1
getfattr -d -m - /data/mysql/ibdata1  # 在目标机上跑,对比输出

如果源机 uid 1000 是 www-data,目标机 uid 1000 是别的用户,不加 --numeric-ids 还会发生“用户漂移”。Nginx/FPM 以错误的属主读文件,直接 502/403。迁移前先核对两端 uid:id www-data

二、--delete 不是增量对齐,是“无差别抹除”

迁移时顺手习惯性加 --delete,结果目标机原来预置的 my.cnfphp-fpm.conf 被删了——因为源机 /data 目录下根本没有这些文件。MySQL 起不来的原因不是数据坏了,而是配置文件没了。

策略很简单:第一次同步永远不加 --delete,确认目标目录确实是源目录的完整镜像后再加。或者先预检要删什么:

rsync -av --delete --dry-run --itemize-changes /data/ root@10.0.0.2:/data/ | grep 'deleting'

看到输出中大量 deleting my.cnf 这类规则外文件,赶紧 Ctrl-C 停下来检查目录结构是否对齐。

三、整盘同步时把 /proc、/sys、/dev 都当成普通文件搬走了

有人图省事直接:

rsync -avHAXA --numeric-ids --delete / root@10.0.0.2:/

没有 --one-file-system(-x) 的情况下,rsync 会把 /proc、/sys、/dev、/run 这些虚拟文件系统的内容当作普通文件同步过去,制造出大量垃圾数据,比如 /proc/cpuinfo 变成目标机上的普通文件、/dev/sda 变成一个空文件或设备节点副本。目标机的 nginx 读到的 CPU 信息是写死的“假文件”,MySQL 的 innodb_flush_method 也可能因设备节点异常而行为错乱。

整盘迁移正确姿势:

rsync -avxHAXA --numeric-ids --delete \
  --exclude='/proc/*' --exclude='/sys/*' --exclude='/dev/*' --exclude='/run/*' --exclude='/tmp/*' \
  / root@10.0.0.2:/

同时别忽略 /etc/fstab:新机器磁盘 UUID 几乎不可能和原来一样,迁移完不改成新 UUID,重启直接进入 busybox emergency mode——这是另一个比 MySQL 起不来更常见的翻车点。

四、同步 MySQL datadir 最隐蔽的坑:binlog 和 auto.cnf 也被带过去了

直接把 /data/mysql 整个 rsync,看起来简单,问题一堆:

  • mysql-bin.* 体积巨大,白白占用网络和磁盘
  • 源库和目标库同时运行时,auto.cnf 里的 server-uuid 会冲突,从库复制直接报 Duplicate server_uuid
  • binlog 同步过去后,目标库的 MASTER_STATUS 和实际数据文件不一致,主从复制位点错乱

同步 datadir 时排除这些文件:

rsync -avHAXA --numeric-ids --partial-dir=/tmp/rsync_tmp \
  --exclude='/data/mysql/mysql-bin.*' \
  --exclude='/data/mysql/auto.cnf' \
  /data/mysql/ root@10.0.0.2:/data/mysql/

同步完,目标机执行:

rm -f /data/mysql/auto.cnf   # 让 mysql 启动时重新生成 server-uuid
chown -R mysql:mysql /data/mysql
systemctl start mysqld

另外强调一次:rsync 在线同步“运行中”的 MySQL datadir,InnoDB 的 redo/undo 文件一致性无法保证。如果停机窗口允许,源库先执行 FLUSH TABLES WITH READ LOCK; 再同步,然后 UNLOCK TABLES;。想不停机就用 Percona XtraBackup,别拿生产库赌 rsync 的运气。

五、--partial 和默认临时文件目录:业务目录闪着一堆 .XXXXXX 垃圾

rsync 传大文件时,默认在目标目录生成隐藏的临时文件,传完再 rename。如果你的服务恰好有 inotify 监听或定时任务扫描目录(比如 PHP 的 opcache.validate_timestamps、集群里的定时打包脚本),这些半截临时文件会被当成“正式文件”读到。

# 把中间产物踢出业务目录
rsync -avHAXA --numeric-ids --partial-dir=/tmp/rsync_tmp /data/ root@10.0.0.2:/data/

还有另一个和 --partial 无关但极常见的报错:rsync: mkstemp failed: No space left on device。很多人下意识去查目标业务盘,发现空间充足,却忽略了 /tmp 分区满了——因为没指定 --partial-dir 时,rsync 会按你在目标机的家目录或临时目录创建临时文件,尤其是大量小文件迁移时,inode 先耗尽。用 df -i 检查 inode,别只看容量。

最后的规范操作模板

迁移 MySQL datadir,接受短时间锁表的情况下,可以直接抄这个流程:

# 源机
mysql -e "FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS;"
rsync -avHAXA --numeric-ids --partial-dir=/tmp/rsync_tmp \
  --exclude='/data/mysql/mysql-bin.*' \
  --exclude='/data/mysql/auto.cnf' \
  /data/mysql/ root@10.0.0.2:/data/mysql/
mysql -e "UNLOCK TABLES;"

# 目标机
rm -f /data/mysql/auto.cnf
chown -R mysql:mysql /data/mysql
systemctl start mysqld

最后用这个命令做最终验证,输出为空或只有文件列表头则两端一致:

rsync -avHAXA --numeric-ids --dry-run --itemize-changes /data/ root@10.0.0.2:/data/ | grep -v '^\./'

如果当时开的是轻云互联同 VPC 下的新实例,这一步可以直接走内网,带宽吃满也不影响线上公网链路,迁完测一把 mysql -e "SELECT 1;"nginx -t,再切流量。

rsync 不是 cp,迁移不是拷贝。把上边五个默认行为想清楚,你的数据搬家才叫迁移,而不是一次裸奔。