news 2026/9/29 16:17:22

CentOS Stream 9 卸载重装 MySQL 8.4.7 并迁移数据到指定盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CentOS Stream 9 卸载重装 MySQL 8.4.7 并迁移数据到指定盘

Linux CentOS Stream 9 一键卸载 MySQL 8.4.7 并重装到指定盘

干了这么多年 Linux 运维,我几乎每个月都能碰到这种场景:服务器刚到手时图省事,MySQL 直接用默认方式装上去,数据一路往/var/lib/mysql里堆。等系统盘告警、df -h一敲发现/已经 100% 的时候,才想起来当初怎么没把数据库放在数据盘上。我这次处理的就是一台 CentOS Stream 9 机器,上面跑着 MySQL 8.4.7,系统盘就剩 800M,而另外挂载了一块 1TB 的数据盘一直闲置。与其用软链接把/var/lib/mysql挪过去,不如直接卸载干净、重装到指定盘,一步到位。这篇文章就把这套“卸载 + 重装 + 数据迁移到指定盘”的流程完整记录一下,并且整理成脚本,下次再遇到同类服务器可以直接抄作业。

不管你是在学 Linux 安装 MySQL 的新手,还是天天跟数据库目录、磁盘布局打交道的运维老手,这篇都值得看完。新手的收获是知道 MySQL 到底怎么卸载才干净、怎么指定数据目录才不出错;老手的收获是我把踩过的坑,尤其是 SELinux 和 socket 路径这两个隐形杀手,全部摊开来讲清楚,避免你重走弯路。

1. 为什么要把 MySQL 重装到指定盘:方案选型与设计思路

1.1 默认安装路径的问题根源

MySQL 在 Linux 上用 RPM 包装完之后,数据目录被固定在/var/lib/mysql,日志写到/var/log/mysql,socket 文件放在/tmp/mysql.sock或者/var/lib/mysql/mysql.sock。这本身没有毛病,问题的关键在于/var属于根分区。很多云主机根分区给的容量就 40G 到 50G,系统本身吃掉一部分,nginx 日志、业务备份再占掉一部分,留给数据库的余量非常有限。数据库一涨起来,根分区直接被打爆。

我见过最典型的故障现场就是:/var/lib/mysql下面的 binlog 和临时文件把根分区写满,MySQL 直接拒绝写入,业务侧大量报错。此时数据库本身没有坏,纯粹是磁盘空间耗尽。运维要做的无非两条路——扩容根分区,或者把数据目录挪到独立的数据盘上。扩容根分区要动云盘、扩分区、扩文件系统,中间停机窗口很大;而把 MySQL 的数据目录搬到一块干净的数据盘,不动系统盘、不扩容、不影响已经挂载的其他服务,明显更划算。

1.2 “重装到指定盘”而不是“软链接挪目录”

把 MySQL 的数据目录搬家,业界常见的做法有三种。

第一种是直接软链接:停库,把/var/lib/mysql整体拷贝到新盘,然后mv /var/lib/mysql /var/lib/mysql.bak,再ln -s /data/mysql /var/lib/mysql。这招在部分系统上确实能跑,但隐患不小:MySQL 升级时 RPM 包里的脚本有时会删掉软链接重建目录,SELinux 对软链接上下文的识别也常有异常;另外 systemd 的ProtectSystem等安全选项在某些版本下会阻止对软链接目录的正常访问。

第二种是 Mount Bind 挂载:把数据盘挂载到/var/lib/mysql目录,看起来路径不变,底层其实是新盘。这个方法不用改配置文件,适用性很广,但如果数据库需要漂移、或者存在多个实例要分目录部署时,bind 挂载管理起来就比较繁琐。

第三种就是我采用的“真重装”:卸载旧 MySQL,重新安装时将数据目录通过datadir配置项显式指向数据盘,比如/data/mysql。这是最干净、最符合官方推荐的方式,不存在任何路径层面的兼容性问题。代价是要停机、要重装、要重新初始化数据目录。但配合自动化脚本,整个流程可以在十分钟内完成,风险完全可控。

1.3 一键脚本的三个设计原则

把整个流程脚本化,最忌讳的就是写一个“看起来能用,其实一跑就崩”的脚本。我在设计这个一键脚本时给自己定了三条死规矩。

第一,备份永远先于破坏。任何情况下,脚本执行到卸载步骤之前,必须先做数据备份,没有备份直接拒绝继续执行。

第二,幂等性。脚本不管是第一次跑、中途失败重新跑、还是对一台已经重装过 MySQL 的机器跑,都不应该产生副作用。比如重复添加官方仓库要能跳过,目标数据目录已存在时不能无脑覆盖。

第三,分段可见。一键脚本不代表黑盒,每个大步骤都要打印清晰的状态提示,并且要求每一步的执行结果都被检查,失败即中断,不能带着错误往下走。

基于这三条原则,整体流程划分为:环境检查、数据备份、卸载清理、仓库配置、安装、目录初始化、配置写入、启动验证八个阶段。下面按实际执行顺序逐步展开。

2. 动手之前的准备:备份、磁盘评估与依赖确认

2.1 盘点现有数据体量

卸载之前必须先搞清楚一个问题:这台机器上的 MySQL 到底有多少数据,删了之后还能不能恢复。我处理的那台机器上跑着好几套应用库,最大的库有 47GB,里面还有一堆统计表。直接在系统里敲:

du -sh /var/lib/mysql mysql -u root -p -e "SELECT table_schema, ROUND(SUM(data_length+index_length)/1024/1024, 2) AS total_mb FROM information_schema.tables GROUP BY table_schema ORDER BY total_mb DESC;"

第一句看总目录占用,第二句从库里按 schema 统计各库容量。两个都能跑的话,数据体量基本就有数了。我遇到的现象是第二句因为库太大、查询慢,差点以为自己连不上数据库,实际是 information_schema 统计时锁表现象,等了十几秒才出结果,不用慌。

2.2 mysqldump 逻辑备份与物理备份双保险

我强烈建议逻辑备份和物理备份各做一份,不要嫌麻烦。逻辑备份用mysqldump导出 SQL 文件,方便重装之后直接导入;物理备份直接把整个/var/lib/mysql拷贝走,防止 mysqldump 在导出过程中遇到个别损坏的表或权限问题导致漏数据。

逻辑备份命令如下:

mkdir -p /backup/mysql_backup mysqldump -u root -p --all-databases --single-transaction --routines --triggers --events --set-gtid-purged=OFF > /backup/mysql_backup/all_databases_$(date +%F).sql

这里说几个容易被忽略的关键参数。--single-transaction配合 InnoDB 可以做一致性快照备份,不锁表,在线执行时不影响业务写入;--routines和--triggers必须加,否则存储过程、触发器全部丢失;--events备份事件调度器;--set-gtid-purged=OFF是 MySQL 8.0/8.4 环境下从非复制实例导出时必须要加的,不然导入时会把 GTID 历史带上,可能引发主从复制冲突。

物理备份更简单也更快:

systemctl stop mysqld cp -rp /var/lib/mysql /backup/mysql_backup/var_lib_mysql_physical_$(date +%F) systemctl start mysqld

停库再拷贝,能保证数据文件处于一致状态。如果业务允许长时间只读,先停库备份完再启动,是最稳的。如果不允许停机,那就用rsync做两次同步,第一次在线同步,第二次短暂锁定写入再同步增量。

2.3 确认目标数据盘的挂载状态

备份做完,接下来要看数据盘。目标盘的挂载点、文件系统类型、剩余空间都需要确认:

lsblk df -hT fdisk -l

我这边的情况是数据盘/dev/sdb已经格式化成了 XFS,挂载在/data,可用空间 980GB。如果你的新盘还没有挂载,那就需要先分区、格式化、写进/etc/fstab做持久挂载,再继续往下走。这里提醒一句,目标挂载目录最好是单独的新目录,比如/data,不要直接挂在/home或/root下面,MySQL 会拒绝把数据目录放到 home 目录路径下,报错信息是Datadir is inside home directory,这个坑在手动初始化时非常常见。

2.4 清理 CentOS Stream 9 的包管理依赖

CentOS Stream 9 默认用 dnf 作为包管理器,官方仓库里就叫mysql-server。我机器上的 MySQL 8.4.7 是通过 MySQL 官方 Yum 仓库装的,所以卸载前确认一下来源:

rpm -qa | grep -i mysql

在有官方仓库的机器上,结果通常包含mysql-server、mysql84-libs、mysql84-common、mysql84-icu-data-files等。卸载时直接dnf remove mysql-server会把服务端去掉,但留下的mysql84-libs等库文件未必会一起移除,需要手动再清理。如果你是通过dnf install mysql-server从 AppStream 装的,那包名可能略有差异,但卸载思路一样。

还有一点非常重要:卸载记录一定要确认,不能漏掉配置目录。RPM 卸载时默认不会删除/etc/my.cnf和/var/lib/mysql数据,这是设计上防止误删的安全机制。但我们的诉求是“重装到指定盘”,必须主动清理这些残留。

3. 卸载脚本的执行逻辑与清理细节

3.1 卸载阶段脚本实现

我把卸载阶段的核心代码提出来,这段可以直接单独跑,也可以合并进一键脚本:

#!/bin/bash # mysql_uninstall.sh # 用途:在 CentOS Stream 9 上彻底卸载 MySQL 8.4.7 set -euo pipefail echo "========== [1/4] 停止 MySQL 服务 ==========" systemctl stop mysqld 2>/dev/null || true systemctl disable mysqld 2>/dev/null || true pkill -9 mysqld 2>/dev/null || true echo "========== [2/4] 确认备份完成 ==========" if [ ! -f /backup/mysql_backup/all_databases_*.sql ] && [ ! -d /backup/mysql_backup/var_lib_mysql_physical_* ]; then echo "错误:未检测到任何备份文件,拒绝卸载!" exit 1 fi echo "========== [3/4] 卸载 RPM 包 ==========" dnf remove -y mysql-server mysql84-server mysql84 2>/dev/null || true dnf remove -y $(rpm -qa | grep -i mysql) 2>/dev/null || true echo "========== [4/4] 清理残留文件 ==========" rm -rf /var/lib/mysql.old_$(date +%s) 2>/dev/null || true mv /var/lib/mysql /var/lib/mysql.old_$(date +%s) rm -f /var/log/mysqld.log /var/log/mysql.log 2>/dev/null || true rm -rf /etc/my.cnf /etc/my.cnf.d 2>/dev/null || true echo "卸载完成。残留文件已重命名为 /var/lib/mysql.old_*,如需找回数据可以从此目录恢复。"

这段脚本里有几个小细节值得展开讲讲。

停服务时我加了一个pkill -9 mysqld,这是防止 mysqld 进程异常驻留。正常systemctl stop可以优雅停机,但一旦遇到卡死的连接或者磁盘 IO 阻塞,stop 会一直卡住,这时候强杀是无奈但有效的办法。考虑到脚本的自动化属性,这一行保留,但日常手动操作时还是建议先systemctl stop多等几秒,不要一上来就 pkill。

备份检查这步,我用的是通配符判断文件是否存在。你可能觉得set -euo pipefail下如果没有任何备份文件会直接退出,不需要额外判断。但实际上 bash 的set -e对if判断内部命令是豁免的,所以需要显式写这个检查。备份检查这段绝非形式主义,我吃了太多教训,卸载脚本里如果没有这层防线,一台忘记备份的机器跑了卸载命令,后果就是彻底凉凉。

dnf remove -y $(rpm -qa | grep -i mysql)这行的威力很大,会把所有含 mysql 字样的包全部移除。如果不加过滤条件,有可能把mysql-connector-odbc等客户端相关包也一并删掉,所以在实际使用时建议把这一行保留,但提前用rpm -qa | grep -i mysql确认一下列表内容。

3.2 为什么用“重命名”而不是“直接删除”数据目录

脚本里把/var/lib/mysql重命名成带时间戳的旧目录,而不是直接rm -rf。这个设计是因为数据目录里可能有你还没意识到的价值。比如 binlog 中可能有某些时间点的增量数据,或者某个库的某些表是 MyISAM 引擎,mysqldump 不一定能完整导出。保留旧目录等于多一个后悔药,而且重命名操作比删除快得多、安全得多。等重装完成、数据验证通过之后,再手动清理这个旧目录也不迟。

清理/etc/my.cnf和/etc/my.cnf.d也是必须的,因为重装之后如果旧配置里还有指向旧 socket 或旧 datadir 的路径,新实例很容易起不来。特别是 CentOS Stream 9 上我遇到过/etc/my.cnf.d/mysql-server.cnf这类分段配置,不删干净的话,改了主配置却总被下面的分段配置覆盖,排查半天才知道原因。

3.3 卸载结果的验证方法

脚本跑完,不要直接进入安装阶段。先验证卸载是否真的干净:

rpm -qa | grep -i mysql # 应无任何输出 which mysql # 应提示命令不存在 ls /var/lib/mysql # 目录已不存在或只剩重命名后的目录

这一步如果发现rpm -qa还有残留,需要重新执行dnf remove。如果which mysql还能找到,说明系统的 PATH 里还有残留的二进制,可能是手动编译安装留下的,这类分布在路径层面不容易清干净,但不影响重装,安装官方 RPM 时会把新版二进制覆盖到/usr/bin/mysql。

4. 重装 MySQL 8.4.7 并指向数据盘:关键配置与初始化

4.1 安装 MySQL 官方仓库

CentOS Stream 9 的 AppStream 仓库自带的 MySQL 版本通常是 8.0 系列,而标题里指定要 MySQL 8.4.7,这个是 8.4 LTS 系列,必须用 MySQL 官方 Yum 仓库来装。

rpm -ivh https://dev.mysql.com/get/mysql84-community-release-el9-1.noarch.rpm

执行完可以用下面的命令确认仓库是否生效:

dnf repolist | grep mysql

正常能看到mysql84-community和mysql84-community-source两个仓库。接下来安装服务端:

dnf install -y mysql-server

等待安装完成。安装过程会把mysqld服务放到/usr/sbin/mysqld,同时生成默认的/etc/my.cnf。这里有个细节:MySQL 8.4 默认的/etc/my.cnf是空配置,绝大多数配置项都依赖默认值,所以后续我们要往里面写入自定义的 datadir 和 socket 路径。

4.2 创建目标数据目录并处理权限

数据盘挂载在/data,那就先建目录、改权限:

mkdir -p /data/mysql chown -R mysql:mysql /data/mysql chmod 750 /data/mysql

chown改成mysql:mysql这一点不用多说,MySQL 的 systemd 服务默认以 mysql 用户运行。但chmod 750可能会被一些人忽略。如果目录权限是 755,其他用户也能进入目录读元数据文件,这不符合最小权限原则;如果设成 700,mysql 用户能访问,但 MySQL 的目录内还会创建临时文件,750 是折中且稳妥的方案。

4.3 修改 my.cnf 核心配置

在/etc/my.cnf里写入:

[mysqld] ###### 基础配置 ###### datadir=/data/mysql socket=/data/mysql/mysql.sock pid-file=/data/mysql/mysqld.pid log-error=/var/log/mysqld.log ###### 字符集与时区 ###### character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-time-zone=+8:00 ###### 连接优化(可选) ###### max_connections=1000 skip-name-resolve=1 innodb_buffer_pool_size=2G [client] socket=/data/mysql/mysql.sock

这里最关键的三个变量是datadir、socket、pid-file。只要 datadir 变了,socket 和 pid-file 建议也一起变,否则会出现一个很隐蔽的故障:mysqld 把 socket 文件放在了/data/mysql/mysql.sock,但客户端默认去/var/lib/mysql/mysql.sock找,结果报ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'。网络热词里正好有这条报错,见过的人绝对不少。所以[client]段里的 socket 也要同步指向新位置,这样本地连接才能正常走通。

skip-name-resolve=1是我习惯加上的配置,它让 MySQL 不再对客户端 IP 做反向 DNS 解析,能减少连接延迟和 DNS 故障导致的连接问题。副作用是user表中的 host 字段必须用 IP 或用localhost,不能用域名,大多数场景都没问题。

default-time-zone=+8:00这块要留意一下,如果你用的是 UTC 时区服务器,不显式指定可能会导致应用侧时间差 8 小时。判断系统时区用timedatectl和 SQL 里的SELECT NOW();对照一下即可。

4.4 SELinux 策略:最容易踩的大坑

CentOS Stream 9 默认开启 SELinux,而且处于 enforcing 模式。MySQL RPM 包的 SELinux 策略默认放行了/var/lib/mysql目录,但一旦我们把 datadir 指到/data/mysql,mysqld 在启动时访问这个新目录就会触发 SELinux denial。日志里通常出现类似:

Jan 10 12:00:01 host mysqld[1234]: Can't open the mysql.plugin table.

或者:

Jan 10 12:00:01 host kernel: audit: type=1400 audit(...): avc: denied { write } for pid=1234 comm="mysqld" name="mysql" dev="sdb1" scontext=system_u:system_r:mysqld_t:s0 tcontext=system_u:object_r:unlabeled_t:s0 tclass=dir

我自己一开始没注意,启动失败跑去看/var/log/mysqld.log,里面啥都没有,但是journalctl -u mysqld能看到 SELinux 的审计日志。这个问题有两种解决办法。

第一种最正规:给新目录配置 mysqld 类型的 SELinux 文件上下文,然后 restorecon。

semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?" restorecon -Rv /data/mysql

如果系统没有semanage命令,先装工具包:

dnf install -y policycoreutils-python-utils

第二种是临时测试时用:

chcon -R -t mysqld_db_t /data/mysql

chcon直接修改目录的 SELinux 标签,但不会持久化,文件系统重新标记后可能会被还原。日常运维建议老老实实用semanage fcontext加restorecon,一次配置永久生效。

如果你觉得 SELinux 太麻烦想直接关掉,那就是另一条路。在/etc/selinux/config里把SELINUX=enforcing改成permissive,重启或执行setenforce 0。但我不建议你为 MySQL 单独关闭 SELinux,因为生产环境开 SELinux 是基本的安全底线,正确配置策略并没有想象中复杂,花几分钟改上下文就能解决,没必要降低整个系统的安全等级。

4.5 初始化数据目录

MySQL 8.4 安装完成之后不会像旧版本那样自动帮你初始化数据目录,需要手动执行mysqld --initialize。有两种初始化模式。

第一种,自动生成临时随机密码:

mysqld --initialize --user=mysql

初始化完成后,临时密码打印在错误日志里:

grep 'temporary password' /var/log/mysqld.log

拿到密码后要尽快登录并修改。

第二种,生成空密码的 root 账号:

mysqld --initialize-insecure --user=mysql

这适合自动化脚本场景,不需要解析日志就能直接登录,然后立即用 SQL 设置新密码。我在一键脚本里用的是--initialize-insecure,因为自动化处理随机密码特别痛苦。

这里必须强调:初始化命令必须在/etc/my.cnf配置完成之后执行,或者配合--datadir=/data/mysql参数显式指定。如果初始化时 my.cnf 还没改,mysqld 会在默认的/var/lib/mysql初始化,你的 datadir 配置就白写了。我遇到过头疼的情况:配置写好了,但忘了清空/data/mysql下的残留文件,执行初始化时报错[ERROR] InnoDB: The innodb_system data file 'ibdata1' must be writable,原因就是旧文件权限不对或残留冲突。重新清空目录后跑就正常了。

5. 启动服务、验证数据目录与恢复备份数据

5.1 启动服务并设置开机自启

配置和初始化都完成之后,执行:

systemctl daemon-reload systemctl enable mysqld --now systemctl status mysqld

systemctl status输出里有Active: active (running)就说明启动成功。此时马上验证 datadir 是否生效:

mysql -uroot -p -e "SHOW VARIABLES LIKE 'datadir';" mysql -uroot -p -e "SHOW VARIABLES LIKE 'socket';"

输出应该指向/data/mysql/和/data/mysql/mysql.sock。如果你用--initialize-insecure初始化,此时 mysql 的 root 密码是空的,立刻修改:

mysql -uroot -p --connect-expired-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourNewStrongPass123!';"

MySQL 8.4 默认的密码策略要求长度、大小写、数字、特殊字符,太简单的密码会直接被拒绝。

5.2 从备份导入数据

重装后的 MySQL 是一张白纸,业务库全部要重新导入。用逻辑备份恢复:

mysql -uroot -p < /backup/mysql_backup/all_databases_20250110.sql

如果备份文件比较大,导入时可以把进度打到日志里:

mysql -uroot -p --force < /backup/mysql_backup/all_databases_20250110.sql 2> /backup/mysql_backup/import_error.log

导入完成之后,务必做一次对比校验:

mysql -uroot -p -e "SELECT table_schema, COUNT(*) AS table_count FROM information_schema.tables GROUP BY table_schema;"

再抽查几张业务大表的行数是否与原备份体现的数量一致。老话讲“没有验证的恢复等于没恢复”,这一步不能省。

5.3 一键整合脚本总览

我把整个流程整合成了一个完整脚本,放在/root/mysql_relocate.sh。结构和前面的分步讲解完全对应,但有几点整合时需要注意:检测是否重复执行、备份检查逻辑复用、日志输出到固定文件。核心框架如下:

#!/bin/bash set -euo pipefail DATA_DISK_MOUNT="/data" DATA_DIR="/data/mysql" BACKUP_DIR="/backup/mysql_backup" MYSQL_ROOT_PASS="YourNewStrongPass123!" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } fail() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*"; exit 1; } # 1. 检查是否 root 执行 [ "$(id -u)" -eq 0 ] || fail "请使用 root 执行" # 2. 检查目标盘的挂载情况 [ -d "$DATA_DISK_MOUNT" ] || fail "数据盘挂载目录不存在 $DATA_DISK_MOUNT" # 3. 备份 log "开始备份所有数据库..." systemctl stop mysqld 2>/dev/null || true mkdir -p "$BACKUP_DIR" cp -rp /var/lib/mysql "$BACKUP_DIR/var_lib_mysql_physical_$(date +%F)" || true systemctl start mysqld 2>/dev/null || true mysqldump -u root -p"$MYSQL_ROOT_PASS" --all-databases --single-transaction --routines --triggers --events --set-gtid-purged=OFF > "$BACKUP_DIR/all_databases_$(date +%F).sql" || fail "备份失败" # 4. 卸载 log "卸载旧版 MySQL..." systemctl stop mysqld 2>/dev/null || true systemctl disable mysqld 2>/dev/null || true dnf remove -y mysql-server mysql84-server mysql84 2>/dev/null || true dnf remove -y $(rpm -qa | grep -i mysql) 2>/dev/null || true [ -d /var/lib/mysql ] && mv /var/lib/mysql "/var/lib/mysql.old_$(date +%s)" # 5. 安装 log "安装 MySQL 8.4.7..." rpm -ivh https://dev.mysql.com/get/mysql84-community-release-el9-1.noarch.rpm 2>/dev/null || true dnf install -y mysql-server # 6. 写入配置 log "写入 my.cnf 配置..." cat > /etc/my.cnf <<EOF [mysqld] datadir=$DATA_DIR socket=$DATA_DIR/mysql.sock pid-file=$DATA_DIR/mysqld.pid log-error=/var/log/mysqld.log character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-time-zone=+8:00 max_connections=1000 skip-name-resolve=1 innodb_buffer_pool_size=2G [client] socket=$DATA_DIR/mysql.sock EOF # 7. 创建数据目录 + SELinux 上下文 log "准备数据目录..." mkdir -p "$DATA_DIR" chown -R mysql:mysql "$DATA_DIR" chmod 750 "$DATA_DIR" semanage fcontext -a -t mysqld_db_t "$DATA_DIR(/.*)?" 2>/dev/null || true restorecon -Rv "$DATA_DIR" # 8. 初始化并启动 log "初始化数据目录..." rm -rf "$DATA_DIR"/* mysqld --initialize-insecure --user=mysql systemctl enable mysqld --now # 9. 修改 root 密码 log "设置 root 密码..." mysql -uroot --connect-expired-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$MYSQL_ROOT_PASS';" # 10. 导入备份 log "导入备份数据..." mysql -uroot -p"$MYSQL_ROOT_PASS" --force < "$BACKUP_DIR"/all_databases_*.sql log "全部完成。建议执行 mysql -uroot -p 登录验证。"

这段脚本里有两个地方是刻意设计的。一个是第 3 步先做物理备份再做逻辑备份,顺序不能反。如果机器上 mysqld 因为磁盘满起不来,逻辑备份导出很可能失败,但物理备份只要磁盘能读就能成功复制。另一个是第 8 步rm -rf "$DATA_DIR"/*,这一步保证mysqld --initialize-insecure不会因旧文件冲突失败。配合前面“备份无论如何都要先完成”的防线,这里的删除是安全的。

6. 常见问题与排查技巧实录

6.1 错误速查表

以下这些错误在我处理过程中基本都会遇到,按频率从高到低整理成一张表,方便你对照排查。

错误现象根本原因解决方法
Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'socket 路径不匹配,服务端与客户端配置不一致确认/etc/my.cnf的[mysqld]和[client]妥协同一个 socket 路径
启动失败,日志出现avc: denied { write } ... mysqld_t ... unlabeled_tSELinux 未放行新 datadirsemanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?"; restorecon -Rv /data/mysql
mysqld: Can't create/write to file '/data/mysql/...' (Errcode: 13 - Permission denied)目录权限不对,或 mysql 用户无写权限chown -R mysql:mysql /data/mysql并确认父目录/data可被 mysql 用户穿越
[ERROR] InnoDB: The innodb_system data file 'ibdata1' must be writable/data/mysql有残留旧文件,或权限不正确清空数据目录内容后重新--initialize-insecure
ERROR 1045 (28000): Access denied for user 'root'@'localhost'密码错误,或者用了临时密码没修改初始化日志找临时密码,或--initialize-insecure后用空密码登录再改密
远程连接 MySQL 报Authentication plugin 'caching_sha2_password' cannot be loaded客户端版本过旧,不支持 MySQL 8.4 默认认证插件升级客户端驱动到新版本,或在服务端为用户设置mysql_native_password

6.2 四个值得写下来的排查心得

第一个心得:任何奇怪的启动失败,先去journalctl -u mysqld看日志,不要只看/var/log/mysqld.log。mysqld 的 error log 在配置阶段可能还没生效,而 systemd journal 记录的是启动瞬间的完整输出,包括 SELinux 审计信息。我以前经常一头扎进/var/log/mysqld.log,里面只有半句话,后来才发现关键信息全在 journal 里。

第二个心得:skip-name-resolve=1加上之后,如果应用原来用主机名连 MySQL,会直接连不上,因为 MySQL 不再做反向解析。报错是Host 192.168.1.10 is not allowed to connect to this MySQL server。解决方式是授权时直接用 IP 地址写 host。如果业务里有大量域名连接,不建议开这个参数。

第三个心得:binlog 占空间的问题很容易被忽视。旧实例可能在/var/lib/mysql里堆积了很多 binlog 文件,每个 1GB,几十个下来就是几十 GB。重装之后如果不打算做基于 binlog 的主从复制,可以在 my.cnf 里加expire_logs_days=7(MySQL 8.4 用binlog_expire_logs_seconds),避免新实例没跑几天又把数据盘占满。

第四个心得:如果你要直接拿旧数据目录恢复(就是物理备份那种方式),千万注意 MySQL 8.4 的auto.cnf文件。这个文件记录了 server UUID,直接复制旧数据目录到新机器后,如果新机器原来的auto.cnf和旧的不一样,需要删除/data/mysql/auto.cnf再启动,否则主从场景下 UUID 冲突会导致复制建立失败。单机运行可能不报错,但为了规范,恢复物理备份时最好删掉自动生成的auto.cnf。

6.3 数据验证时的关键一步

安装、导入完成后,还有一步容易被忽略:验证旧目录中的库是否都已经出现在新实例中。我的做法是写了一个快速比对脚本:

mysql -uroot -p -N -e "SHOW DATABASES;" | grep -v -E "^(information_schema|performance_schema|mysql|sys)$" | sort > /tmp/db_new.list

然后再从备份目录的物理备份中,直接读目录名称:

find /backup/mysql_backup/var_lib_mysql_physical_* -maxdepth 1 -type d | awk -F/ '{print $NF}' | grep -v -E "^\.|sys|undo|binlog|tmp" | sort > /tmp/db_old.list diff /tmp/db_new.list /tmp/db_old.list

如果 diff 有输出,说明有库没有恢复进来,需要针对性处理。这个方法不复杂,但能救命。有一次我导入时因为 SQL 文件里存在已删除表的误操作,某个库竟然没被导进来,如果没有这步比对,业务上线半天后才会暴露问题。

7. 最后再说一点实际体会

这套流程我在生产环境上完整跑过不止一次,每次都能顺利收尾,但每次也都会因为环境差异多一些新的发现。比如有的机器挂载点是/mnt/data,有的数据盘格式是 ext4,有的系统里还残留着低版本的 MySQL 客户端,这些都会让脚本的适配层不断变厚。我个人最大的体会是:卸载和重装本身不难,难的是做到“不丢数据、不破坏环境、出了问题能回滚”。所以就算你不需要一键脚本,也请务必保留旧的/var/lib/mysql重命名目录,至少保留一个月再清理。磁盘便宜,数据无价,在这个事情上多留一手永远不亏。

另外想提醒的是,脚本里的 root 密码、缓冲池大小、时区这些参数,换成你自己的环境时一定不要照抄。密码策略、内存大小、业务时区都是高度个性化的,直接套用默认值最容易在后续维护中踩坑。把这套脚本当成一个骨架,根据实际场景调整血肉,才是正确的用法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 16:15:49

PyCharm与conda环境管理实战:解决pip装包后Import Error的常见坑

1. 从"包装不上"说起&#xff1a;先搞懂PyCharm、conda、环境的三角关系 先说一个我几乎每周都会看到的求助场景&#xff1a;在PyCharm里新建了一个conda环境&#xff0c;然后打开Terminal敲 pip install xxx &#xff0c;下载转圈、提示Successfully installed&am…

作者头像 李华
网站建设 2026/9/29 16:15:44

C++游戏开发实战:SDL2马里奥源码解析与跨平台编译

简介&#xff1a;本资源是一份基于C实现的经典平台游戏《超级玛丽》&#xff08;超级马里奥&#xff09;开源源码工程&#xff0c;面向游戏开发初学者与C实践者&#xff0c;旨在通过完整可运行项目理解2D游戏核心架构与编程范式。压缩包共49个文件&#xff0c;包含6个cpp与9个h…

作者头像 李华
网站建设 2026/9/29 16:15:31

反转链表LeetCode 206:三指针迭代与递归详解

反转链表&#xff0c;LeetCode 206&#xff0c;大概是算法题海里最被人低估的一道题。做了这么多年面试官&#xff0c;我和同事私下核对过很多次&#xff1a;能把这道题写出两种解法的人&#xff0c;链表基本不会再出大问题&#xff1b;写不出来的&#xff0c;后续环节十有八九…

作者头像 李华
网站建设 2026/9/29 16:15:12

知网AIGC检测不通过?四步改写助你复检通过

上个月答辩预审前一周&#xff0c;我收到导师转来的检测报告截图&#xff0c;AIGC值一栏是个刺眼的红色数字&#xff1a;68%。旁边附了一句"疑似AI生成内容占比过高&#xff0c;请修改后复检"。那会儿距离提交最终稿只有8天&#xff0c;我整个人都是懵的——论文里每…

作者头像 李华
网站建设 2026/9/29 16:13:04

数字IC后端STA必修:OCV与timing derate配置详解

1. 为什么OCV和timing derate是数字IC后端绕不开的坎做数字IC设计的同行都有个共识&#xff1a;前端RTL写得再漂亮&#xff0c;最后能不能signoff&#xff0c;很大程度上取决于STA&#xff08;Static Timing Analysis&#xff09;做得够不够扎实。而提到STA&#xff0c;PrimeTi…

作者头像 李华
网站建设 2026/9/29 16:12:49

自动分区+冷分区迁移+压缩:Oracle流水表存储与查询性能优化实践

接手过一张按天自动分区的流水表之后&#xff0c;我是真的体会到了“自动分区很省心&#xff0c;但省不了心”。自动分区帮你把“每个月/每天手工建分区”的重复劳动干掉了&#xff0c;可它不会替你考虑&#xff1a;旧分区还在昂贵的存储上躺着&#xff0c;查询还是会扫过大量历…

作者头像 李华