可靠批量升级MySQL需四步:一是扫描配置文件提取端口并验证进程活跃性;二是5.7→8.0用--upgrade=FORCE,8.0.x→8.0.y检查字典表;三是用parallel控制并发与熔断;四是按实例隔离备份路径及显式dump参数。

确认 MySQL 实例发现方式是否可靠
批量升级的前提是能准确、稳定地识别出所有待升级的实例,不能靠手动写死 host:port 列表。常见错误是脚本硬编码 IP 或用 ps aux | grep mysqld 解析进程,结果漏掉 systemd 管理的实例、容器内实例或非标准端口实例。
推荐做法是统一依赖配置中心或静态清单文件(如 JSON/INI),但更轻量且兼容性强的方式是扫描 /etc/my.cnf 及其 !includedir 下的配置文件,提取 socket 或 port 字段:
find /etc/my.cnf* /etc/mysql/conf.d/ -name "*.cnf" 2>/dev/null | \
xargs -I{} sed -n '/^\[mysqld\]/,/^$/s/^port[[:space:]]*=[[:space:]]*\([0-9]\+\).*/\1/p' {} 2>/dev/null | \
sort -u注意:必须验证每个端口对应的是活跃的 MySQL 进程(用 ss -tlnp | grep :$PORT),否则会把已停用配置当活实例处理。
升级前必须验证 mysql_upgrade 兼容性
MySQL 5.7 升级到 8.0 后,mysql_upgrade 已被弃用;8.0.16+ 版本改由服务启动时自动执行数据字典升级。若脚本仍调用 mysql_upgrade,会报错 Command 'mysql_upgrade' not found,且可能中断升级流程。
实操建议:
- 对 5.7 → 8.0 升级:停库后替换二进制,启动时加
--upgrade=FORCE参数确保强制校验 - 对 8.0.x → 8.0.y 小版本升级:无需手动运行升级命令,但需检查
SELECT VERSION()和SELECT * FROM mysql.innodb_index_stats是否可查(避免字典损坏) - 所有升级前执行
mysqld --version和mysqld --verbose --help | grep "Server version"双重确认目标版本
控制并发与失败熔断逻辑不能只靠 for 循环
直接用 for host in ${HOSTS[@]}; do upgrade_one $host; done 会导致单点失败阻塞全部流程,也无法限制并发数。生产环境至少要满足:失败不超过 2 台即暂停、同一台重试不超过 3 次、并发数 ≤3。
简单可行方案是用 parallel + 自定义 wrapper:
export MYSQL_UPGRADE_TIMEOUT=600
export MAX_RETRY=3
cat hosts.txt | parallel -j3 --retries 3 --halt now,fail=2 \
'bash -c "until ./do_upgrade.sh {} || [ \$(($(cat /tmp/retry_{} 2>/dev/null || echo 0)) -ge $MAX_RETRY) ]; do echo \"retry $(($(cat /tmp/retry_{} 2>/dev/null || echo 0) + 1))\" > /tmp/retry_{}; sleep 30; done"'关键点:--halt now,fail=2 表示任意时刻最多允许 2 个任务失败就立即终止整个批次;/tmp/retry_X 记录重试次数,避免无限循环;超时用 timeout $MYSQL_UPGRADE_TIMEOUT 包裹主升级命令。
备份路径和 mysqldump 选项必须按实例隔离
很多脚本用全局 BACKUP_DIR=/backup 导致多实例备份文件覆盖或权限冲突。更严重的是,未指定 --set-gtid-purged=OFF(GTID 环境下)或遗漏 --skip-triggers(含复杂触发器时 dump 失败),会让备份静默不全。
每个实例应动态生成独立备份目录,并显式控制 dump 行为:
- 目录结构:
/backup/$(hostname)-$PORT-$(date +%Y%m%d_%H%M%S) - 必选参数:
mysqldump --single-transaction --routines --events --triggers --set-gtid-purged=OFF --all-databases - 跳过系统库时不能只写
--ignore-table=mysql.*,而要用--ignore-table=mysql.columns_priv --ignore-table=mysql.db ...显式排除(因 8.0 新增了role_edges等表)
真正麻烦的是备份后校验——不是看文件大小,而是用 zcat backup.sql.gz | head -1000 | grep "Dump completed" 确认结束标记存在,再用 gunzip -t 验证压缩完整性。
升级脚本最难的不是执行命令,而是判断“这台到底算升成功了没有”:进程起来 ≠ 监听端口 ≠ 能连上 ≠ 能执行 SELECT 1 ≠ 数据字典一致。每一步都得加超时和重试,且日志必须带实例标识,否则半夜告警时根本分不清是哪台挂了。


















