我干数据库运维这些年,经常遇到一个挺尴尬的需求:同一个开发机上,老项目用的是 MySQL 5.7,新项目非得上 8.0 的新特性,两边都是线上在跑的业务,谁也不能迁。更别说有些公司内部历史包袱重,生产环境同时存在 5.6、5.7、8.0 三个大版本,本机要是不能同时模拟这套组合,联调测试根本没法定。
所以“一台机器装两个 MySQL 版本”根本不是闲着没事折腾自己,而是实际工作中非常典型的场景需求。这篇内容我直接把我平时在一台 Windows 服务器和一台 Linux 服务器上分别部署 5.7 和 8.0 的具体做法、踩过的坑、以及最终的切换方案摊开来讲,保证你跟着操作能一次搞定。
1. 双版本共存的核心思路与方案选择
1.1 为什么非要装两个版本,而不是直接升级
先说一个经常被问到的点:既然新版能向下兼容,为什么不直接把老版本升级了?这里面有个特别现实的问题,MySQL 的所谓兼容是 SQL 层面的,不是实例之间的随意替换。很多老项目用的是 MyISAM 表,5.7 还能跑得挺好,到了 8.0 不少默认配置直接变了,比如认证插件从 mysql_native_password 换成了 caching_sha2_password,老应用用的旧版 JDBC 驱动根本连不上。还有 sql_mode 默认值的变化,8.0 默认带 ONLY_FULL_GROUP_BY,以前那种写 half 一不小心就 bt 的 SQL 在 5.7 里没声音,换上 8.0 直接报错。这就导致很多项目根本不敢随便动版本,而新项目又巴不得用上 8.0 的窗口函数、CTE 和更好的优化器。两边的诉求僵在这,多版本共存就成了唯一的务实解。
1.2 双版本方案的对比:容器化 还是 多实例
遇到多版本需求,第一反应是上 Docker 容器跑 MySQL,确实是最干净的方法,一条命令拉镜像、起容器就完事,版本隔离得也彻底。但容器方案在两类场景下比较吃亏:一类是公司内网机器没有外网权限,无法直接拉取镜像,离线导入镜像包又麻烦;另一类是你需要长期在本地开发、经常跑一些大数据量的 SQL,容器里磁盘 IO 和路径映射做不好,性能损失和调试成本都是实打实的。所以不少老运维骨子里还是倾向于用免安装版或者二进制包,在裸机上跑双实例,配置文件、数据目录、端口全部独立,想停哪个停哪个,遇到诡异问题还能直接看物理日志排查,心里有底。
1.3 核心原则:一个版本一个目录一套配置
双版本共存之所以很多人搞炸,就是脑子里没绷住一条线:两个版本必须完全独立,所有相关资源都要分开。具体来说就是三个东西不能共用:端口不能共用、数据目录不能共用、配置文件不能共用。Windows 下用 my.ini,Linux 下用 my.cnf,一个版本一份,里面各写各的端口、socket、路径。另外 Windows 下还要注意注册为系统服务时名称不能一样,Linux 下 systemd 的 service 单元文件也要拆开。只要守住了“各用各的”这条底线,剩下的就是按部就班地配置,并不复杂。下面我分 Windows 和 Linux 两套场景来讲,你在哪台机器上操作就直接对照哪个小节,思路是一模一样的。
2. 动手之前:版本下载与关键参数规划
2.1 版本选择:ZIP 包还是安装包
Windows 平台强烈建议下载 ZIP Archive 免安装版,千万不要下载那个图形化 MSI 安装包。MSI 安装包默认会往 Program Files 目录塞,安装时还会自动配置系统服务,装第二个版本的时候经常出现配置界面冲突,而且卸载时容易把原先好好的服务一起干掉。ZIP 版解压即用,自己手动配置次数多一次,但干净可控,出问题也知道是什么原因。我这边 5.7 用的版本号是 mysql-5.7.44-winx64.zip,8.0 用的是 mysql-8.0.36-winx64.zip,这两个都是相对稳定的版本,推荐直接用。
2.2 端口、数据目录、Socket 这三件套
装双版本之前,先在心里做好一个简单的资源规划表,后面配置时按表填就行,不容易乱。
| 资源项 | MySQL 5.7 | MySQL 8.0 |
|---|---|---|
| 安装目录 | D:\mysql-5.7.44-winx64 | D:\mysql-8.0.36-winx64 |
| 数据目录 | D:\mysql-5.7.44-winx64\data | D:\mysql-8.0.36-winx64\data |
| 端口 | 3306 | 3307 |
| 服务名 | MySQL57 | MySQL80 |
| 字符集 | utf8mb4 | utf8mb4 |
端口这个点要单独说一下:如果两个版本里有一个是公司标准的默认端口使用习惯,那你最好把不常用的那个换掉。比如团队习惯用 3306 连库,就保留 5.7 在 3306,8.0 换到 3307。如果两边都是新项目,那就随便,只要不冲突就行。Linux 下还有一个 socket 文件要考虑,5.7 用 /tmp/mysql57.sock,8.0 用 /tmp/mysql80.sock,别共用同一个,否则客户端连接时经常乱套。
2.3 服务名和命令工具的环境变量处理
Windows 下把 MySQL 注册成系统服务,是为了方便用 net start 和 net stop 来控制启动停止。安装多版本时,服务名绝对不能用默认的 MySQL,两个版本都用同一个名字,后面的安装会直接覆盖掉前面的服务,到时候大概率一脸懵。我的习惯是带版本号命名:MySQL57、MySQL80,一眼就能认出是哪个实例。环境变量方面,PATH 里最终只能保留一个版本的 bin 目录,否则命令行输入 mysql -uroot -p 时到底调起哪个版本全靠缘分。我的做法是 PATH 里放 8.0 的 bin,需要连 5.7 时就用全路径或者直接 mysql -h127.0.0.1 -P3307 显式指定端口,这样最不容易混淆。
3. Windows 环境下的双版本详细安装流程
3.1 初始化 MySQL 5.7 实例(端口 3306)
先把 ZIP 包解压到指定目录,然后在 D:\mysql-5.7.44-winx64 下新建一个 my.ini 文件。这里特别提醒一句,解压后的目录默认是没有 my.ini 的,很多人第一次装会直接双击运行 mysqld.exe,然后系统弹个报错说缺少配置文件,这不代表装失败了,只是你没有给这个实例定义任何启动参数。我的 my.ini 内容比较精简,但其实每一条都是有讲究的:
[mysqld] port=3306 basedir=D:/mysql-5.7.44-winx64 datadir=D:/mysql-5.7.44-winx64/data character-set-server=utf8mb4 default-authentication-plugin=mysql_native_password [client] port=3306 default-character-set=utf8mb4注意 basedir 和 datadir 里的斜杠方向,用正斜杠最稳,反斜杠有时候会被转义解析出问题。character-set-server 设成 utf8mb4 是为了避免后面创建表出现中文乱码。default-authentication-plugin 这一项建议在 5.7 里显式写上 mysql_native_password,因为后面如果要迁移或者老客户端连接,这个插件兼容性最好。写好配置文件后,用管理员身份打开 CMD,进入 bin 目录执行初始化:
mysqld --defaults-file=D:/mysql-5.7.44-winx64/my.ini --initialize-insecure这里用的是 --initialize-insecure,意思是初始化数据目录时给 root 账号设一个空密码。如果不带 insecure 参数,MySQL 会生成一串随机密码写在日志里,第一次登录还要去翻错误日志找密码,特别烦。Insecure 模式初始化完成后直接用 mysql -uroot 就能登录,然后你再自己 ALTER USER 改密码,这个流程是最顺手。初始化过程一般 10 到 20 秒,结束后检查一下 data 目录下是不是生成了一堆文件,再确认一下有没有 .err 后缀的日志文件,这个日志就是后续排查问题的第一现场。
3.2 把 5.7 注册成 Windows 服务
继续在管理员 CMD 里执行:
mysqld --defaults-file=D:/mysql-5.7.44-winx64/my.ini --install MySQL57执行完如果没有报错,在服务管理器里应该能看到一个叫 MySQL57 的服务。这里有个细节:注册服务时 --defaults-file 参数必须写完整路径,而且路径里不要用中文和空格,否则服务启动时读不到配置文件,会直接启动失败。注册成功后再启动服务:
net start MySQL57启动时如果报“服务名无效”或者“服务没有响应控制功能”,多半是两种原因:一是注册服务时路径写错,二是 data 目录初始化失败。去 Windows 事件查看器的应用程序日志里能捞到详细报错,但更直接的还是看 data 目录里的 .err 日志文件,通常第一行就会写清楚为什么启动不了。
3.3 初始化 MySQL 8.0 实例(端口 3307)
接下来同样解压 8.0 的 ZIP 包到 D:\mysql-8.0.36-winx64,然后在新目录下创建 my.ini。8.0 的配置和 5.7 最大的区别就是端口、目录,以及认证插件这块。8.0 默认的认证插件已经是 caching_sha2_password,如果你没有老客户端的兼容压力,保持默认就好,不专门配置 default-authentication-plugin 这一项。如果确实要兼容老项目,比如比你手头 8.0 还老的 JDBC 版本,可以像我一样在配置里写上:
[mysqld] port=3307 basedir=D:/mysql-8.0.36-winx64 datadir=D:/mysql-8.0.36-winx64/data character-set-server=utf8mb4 mysqlx-port=33080 [client] port=3307 default-character-set=utf8mb4注意这里多了一个 mysqlx-port=33080。MySQL 8.0 自带 X Plugin,默认监听 33060 端口,同一台机器上如果 5.7 没有占这个端口那可能没问题,但如果你机器上还跑了别的组件占了 33060,启动 8.0 时会报端口冲突。我习惯显式指定 mysqlx-port,彻底避免这个隐性问题。初始化命令同样用:
mysqld --defaults-file=D:/mysql-8.0.36-winx64/my.ini --initialize-insecure8.0 的初始化时间和 5.7 差不多,完成后检查 data 目录。然后注册服务并启动:
mysqld --defaults-file=D:/mysql-8.0.36-winx64/my.ini --install MySQL80 net start MySQL80到这里,两台“精神分裂”的 MySQL 已经各自跑起来了。你别慌着开始建库,我建议先分别验证一下端口监听状态。执行 netstat -ano | findstr 3306 和 findstr 3307,确认两个端口有没有同时处于 LISTENING 状态。这是排查双版本问题最直观的手段,比反复刷客户端界面要靠谱得多。
3.4 Windows 下如何优雅地切换使用两个版本
服务都起来了,接下来就是怎么连的问题。我平时习惯用命令行客户端,但区分连哪个实例就靠端口和 IP 的组合:连接命令后面加 -h127.0.0.1 -P3307 就是连 8.0,加 -h127.0.0.1 -P3306 就是连 5.7。如果你用的工具是 Navicat 或者 DBeaver,新建连接时填不同的端口就行,这两个工具都支持同时配置多个 MySQL 连接,并不需要在界面上做什么版本切换。
还有一个容易被忽略但很实用的切换方式:你可以直接停掉其中一个服务,然后单独用另一个版本占默认端口。比如临时要把 8.0 的端口从 3307 改成 3306,那么先把 MySQL57 服务停止,再改 8.0 的 my.ini 端口为 3306,重启 MySQL80 服务即可。但这种做法不建议经常用,因为端口切换会牵动数据连接串和相关配置,搞不好就忘了改回来,留着两个固定端口长期共存反而更省心。
4. Linux 环境下的双版本部署实操
4.1 使用通用二进制包,避免 RPM 冲突
Linux 下装多版本 MySQL 比 Windows 还要更常见。服务器上南美大型企业配置复杂,但规则倒是很清晰:用官方提供的 Generic Linux 二进制包,不要用 yum/apt 的仓库包。仓库里的 mysql-server 安装后会把文件分散到 /usr/bin、/var/lib/mysql 这些位置,再装第二个版本时,包管理器和文件路径冲突能让你怀疑人生。Generic 包解压之后是个完整的目录,二进制、库文件、支持文件都在里面,天然适合多实例。
我在一台 CentOS 7 上做过 5.7 + 8.0 双实例部署,目录规划如下:
| 资源项 | MySQL 5.7 | MySQL 8.0 |
|---|---|---|
| 二进制目录 | /usr/local/mysql-5.7.44 | /usr/local/mysql-8.0.36 |
| 数据目录 | /data/mysql57 | /data/mysql80 |
| 配置文件 | /etc/my-5.7.cnf | /etc/my-8.0.cnf |
| 端口 | 3306 | 3307 |
| socket | /tmp/mysql57.sock | /tmp/mysql80.sock |
| 服务单元 | mysqld57.service | mysqld80.service |
4.2 创建专用用户和数据目录
先下载对应版本的 Generic 二进制包并解压到 /usr/local 下,然后创建数据目录、设置好属主。这一步不能省略,也别偷懒直接用 root 跑 MySQL。
groupadd mysql useradd -r -g mysql -s /bin/false mysql mkdir -p /data/mysql57 /data/mysql80 chown -R mysql:mysql /data/mysql57 /data/mysql80这里创建了一个不能登录的系统用户 mysql,MySQL 进程将以这个用户身份运行,这是安全基线的一部分。要是用 root 直接跑,万一应用层 SQL 注入拿到权限,攻击者就直接拿到数据库进程的权限了,风险太大。
4.3 5.7 和 8.0 的 my.cnf 配置要点
Linux 下的配置文件同样要区分开,5.7 的 /etc/my-5.7.cnf 内容我一般这样写:
[mysqld] user=mysql port=3306 basedir=/usr/local/mysql-5.7.44 datadir=/data/mysql57 socket=/tmp/mysql57.sock character-set-server=utf8mb4 pid-file=/data/mysql57/mysqld.pid log-error=/data/mysql57/mysql57.errwrite 一个容易被忽略的细节:socket 文件路径一定要明确指定,不要用默认的 /var/lib/mysql/mysql.sock,否则两个实例会争抢同一个 socket 文件。客户端连接时通过 -S /tmp/mysql57.sock 才能连到具体实例。log-error 这个参数也建议显式设置,MySQL 的报错信息会统一写到这个文件里,排查问题不用到处翻日志。
8.0 的 /etc/my-8.0.cnf 与 5.7 大体相同,端口改为 3307,路径改成自己对应的,再注意加一行:
[mysqld] ... port=3307 datadir=/data/mysql80 socket=/tmp/mysql80.sock mysqlx-port=330808.0 的缓存和索引默认参数改了不少,但双实例部署时正式库的参数调优先放到后面根据业务再调。只要把路径、端口、socket 这几项独立开,基本就成功了一大半。
4.4 初始化两个实例
5.7 的初始化,进入 /usr/local/mysql-5.7.44/bin 目录执行:
./mysqld --defaults-file=/etc/my-5.7.cnf --initialize-insecure --user=mysql8.0 的初始化同理:
./mysqld --defaults-file=/etc/my-8.0.cnf --initialize-insecure --user=mysql注意一点:如果数据目录里已经有文件,初始化命令会报错提示目录非空。如果之前初始化失败想重来,先确保数据目录删干净再执行。初始化完成后,数据目录下会生成大量系统数据库文件,并且 root 密码为空,这一步代表初始化动作生效了。
4.5 用 systemd 管理双实例
CentOS 7 及以上环境建议编写两个独立的 systemd 服务单元,比直接用 mysqld_safe 启动更符合常态运维习惯。我的 mysqld57.service 内容如下:
[Unit] Description=MySQL 5.7 Server After=network.target [Service] Type=simple User=mysql Group=mysql ExecStart=/usr/local/mysql-5.7.44/bin/mysqld --defaults-file=/etc/my-5.7.cnf LimitNOFILE=65535 Restart=on-failure [Install] WantedBy=multi-user.targetmysqld80.service 的 ExecStart 换成 8.0 的 mysqld 路径和配置文件即可。把两个服务单元放到 /etc/systemd/system/ 目录下,然后执行:
systemctl daemon-reload systemctl start mysqld57 systemctl start mysqld80 systemctl enable mysqld57 systemctl enable mysqld80启动后先用端口验证:
ss -lntp | grep 3306 ss -lntp | grep 3307两条命令输出里应该能看到两个 mysqld 进程分别监听各自端口。如果发现两个进程只监听了一个端口,或者 3306 端口被一个进程同时占用,优先去对应配置文件对应的 .err 日志文件里看有没有端口绑定报错。
4.6 登录验证与本地 socket 连接
Linux 下用命令行连接裸机 MySQL 有个常踩的坑:直接执行 mysql -uroot 不加 -S 时,客户端会去找默认 socket 路径,而那个路径很可能和你配置的 socket 不一致,结果报一个Can't connect to local MySQL server through socket。解决办法是显式指定 socket 文件:
/usr/local/mysql-5.7.44/bin/mysql -uroot -S /tmp/mysql57.sock /usr/local/mysql-8.0.36/bin/mysql -uroot -S /tmp/mysql80.sock或者用 TCP 方式指定端口:
mysql -h127.0.0.1 -P3306 -uroot mysql -h127.0.0.1 -P3307 -uroot我习惯用 TCP 方式,因为后续开发环境的连接串基本都写的 IP 加端口,这样验证和实际运行的环境更接近。
5. 双版本实战中最容易踩的坑(排查实录)
5.1 端口冲突和连接失败的排查思路
最常见的问题就是第二个 MySQL 启动时直接报Bind on TCP/IP port: Address already in use。这个其实不难解决,先用netstat -ano | findstr 3307(Windows)或ss -lntp | grep 3307(Linux)看端口被谁占用。如果确实有进程在监听,说明你配置文件里的端口跟现有服务冲突,换个端口就好。还有一种特殊情况:端口没被占,但启动时依然报错一直起不来,这时候去 data 目录对应的 .err 日志里看,日志里会明确告诉你 bind 失败是在 IPv6 还是 IPv4 层,大多数情况下,MySQL 同时 bind 了 :: 和 0.0.0.0,如果系统禁用了 IPv6 也会导致诡异情况。我遇到过一台系统把 IPv6 关了,MySQL 在绑定 IPv6 失败后干脆连带 IPv4 也起不来,这种就需要在配置文件里加bind-address=127.0.0.1,让 MySQL 只监听 IPv4。
5.2 PATH 环境变量导致命令全是同一个版本
双版本装好后,很多人会发现自己执行 mysql -V 永远只显示一个版本,而且很容易搞混当前连的是哪个。这个问题的根源在于 PATH 环境变量只保留了一个 MySQL 的 bin 目录,系统会优先找到第一个匹配的 mysql 命令。解决办法有几个思路:一是在 PATH 里保留你日常使用更频繁的那个版本;二是少依赖 PATH,直接用完整路径;三是干脆在用户目录下建一个别名映射,比如把 mysql57 指向 5.7 的 mysql 命令,把 mysql80 指向 8.0 的 mysql 命令。我个人的习惯是 PATH 保留 8.0,然后给 5.7 的 mysql 命令建一个别名,这样进终端后输入别名就能准确连到对应版本,不用记长串路径。
5.3 配置文件被读串了
如果两个实例都启动成功,但你发现改了 my.ini 里的参数后重启没生效,十有八九是配置文件读取顺序搞混了。MySQL 的默认配置读取顺序很“诡异”,它会依次读取 /etc/my.cnf、/etc/mysql/my.cnf、系统变量里的默认路径,以及命令行 --defaults-file 指定的文件。如果你把两个版本的配置都写在同一个默认文件里,那后启动的实例会读到这个文件,然后部分参数互相覆盖,完全不知道当前实例实际生效的是哪套配置。最稳妥的做法就是每次启动都显式用 --defaults-file 参数指定配置文件,并且启动脚本、systemd 单元、注册服务命令里全部保持同一个路径,不要让 MySQL 有机会去读全局默认配置。
5.4 初始化失败或数据目录半成品问题
初始化失败是很常见的事,尤其是 Windows 下直接在解压目录下执行 mysqld --initialize 时,data 目录已经生成了几个半成品文件,然后你以为清理了 data 目录重来,结果发现还是报错。原因可能是 data 目录里有隐藏文件如 .err、.pem,或者目录本身没删干净。Windows 下建议直接在资源管理器里把整个 data 目录删除,再重新创建;Linux 下则用 rm -rf /data/mysql57/* 清空,但删除前要确认目录路径别写错,否则会把不该删的删了。还有一种情况是初始化时报 Can't create directory,大概率是权限问题,Linux 下用 chown 把数据目录属主改成 mysql 用户就能解决。
5.5 连接认证方式不兼容导致应用报错
数据库本地双版本装好了,结果应用连 8.0 时一堆报错,最常见的错误是Authentication plugin 'caching_sha2_password' cannot be loaded。这是因为 8.0 默认认证插件是 caching_sha2_password,而老版本驱动不认识。解决方式有两个:一个是把用户的认证插件改回 mysql_native_password,在 8.0 实例里执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;另一个是升级应用使用的 JDBC 驱动到 8.0.x 以上版本,驱动升级后就能直接支持 caching_sha2_password。我建议能升级驱动就升级驱动,因为 8.0 团队后续版本对老认证插件的支持会越来越弱,长期靠改认证方式维持不是正经路子。
5.6 备份恢复和跨版本导数据的小提示
双版本共存有一个隐藏福利:你可以直接把 5.7 的库导出,再导入 8.0,来做一次真实的升级演练。用 mysqldump 导出时,建议加 --set-gtid-purged=OFF 这个参数(如果开启了 GTID),否则导入 8.0 时会产生一堆 GTID 相关报错。还有字符集问题,导出时显式带上 --default-character-set=utf8mb4,导入时也一致,避免中文乱码。跨大版本导数据时,有个别存储过程和触发器可能出现兼容问题,导进去之后记得抽查一下关键存储过程能否正常执行。我在一次项目迁移中,就是靠双版本环境反复导入验证,才赶在正式升级前发现 8.0 的 sql_mode 变化把一个老存储过程的 GROUP BY 写法卡住了。
6. 日常使用的收尾小技巧
最后分享几个我自己的实操习惯。第一个,启动实例前养成看一眼端口的好习惯,尤其折腾过多个 MySQL 的机器,两条 netstat 命令能避免绝大多数连通性困扰。第二个,配置文件里每一个路径都用绝对路径,别偷懒写相对路径,不然启动方式一变路径就失效了。第三个,给两个实例各自建一个独立的备份目录,导出文件按版本分开放,时间久了也不会弄混。
还有一个很实用的习惯:把两个版本的常用命令写进一个简单的启动脚本或者 bat 文件。Windows 下我建了两条快捷命令,一个名叫 mysql57,内容指向 5.7 的 bin 目录下 mysql.exe 并加 -P3306,另一个指向 8.0 的 bin 目录加 -P3307。Linux 下就在 /etc/profile.d/ 里加两个 alias,效果一样。这种小动作看着不起眼,但极大地减少了日常操作时连错库的可能。毕竟版本隔离再彻底,人的记忆总有不靠谱的时候。