升级到 MySQL 8 之后,为什么老客户端连不上了
这几乎是每个从 MySQL 5.7 升级到 8.0 的人都会踩的坑:数据库明明升级成功了,服务也正常启动,但一用老的客户端、老的监控脚本、或者某个用了很久的 PHP 程序去连,就报一个让人一头雾水的错:
ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded:
/usr/lib/mysql/plugin/caching_sha2_password.so: cannot open shared object file或者干脆报 Authentication plugin 'mysql_native_password' cannot be loaded。更迷惑的是:用命令行新装的 mysql 客户端能连,用程序却不行;用程序能连,换个老版本工具又不行。这背后的真凶,就是 MySQL 8 更换了默认的认证插件。
这篇文章把这件事从根上讲清楚:caching_sha2_password 是怎么来的、为什么会导致连不上、怎么安全地迁移、以及为什么「改回 mysql_native_password」是个需要谨慎对待的临时方案。配一套可以一步步照做的排错和迁移流程。
一、MySQL 8 到底改了什么:默认认证插件的换代
在 MySQL 5.7 及更早版本里,用户密码的默认认证方式是 mysql_native_password。它的核心是 SHA-1 哈希,客户端和服务器之间通过一套挑战-应答(challenge-response)机制验证密码,密码本身不通过网络明文传输。这套机制用了几十年,兼容性极好,几乎所有语言的 MySQL 驱动都实现了它。
但 SHA-1 在今天已经不安全了——研究表明可以在可接受的成本下构造 SHA-1 碰撞。出于安全考虑,MySQL 8.0 把默认认证插件换成了 caching_sha2_password,它基于 SHA-256,强度大幅提升,并且引入了缓存机制来弥补强哈希带来的性能开销。
问题就出在这个「更换默认值」上。MySQL 8 安装后,新建的用户默认使用 caching_sha2_password。而老版本的客户端库(尤其是早于 8.0 的那批驱动)根本不认识这个插件——它们不知道怎么做 SHA-256 的挑战应答,也不知道怎么在连接时协商。于是就有了开头那两类报错。
二、caching_sha2_password 的工作机制:为什么「第一次连接慢,之后快」
理解这个插件的工作流程,是理解所有现象的关键。它的认证分两种路径:
- 快速路径(缓存命中):用户第一次成功认证后,服务器会把密码的 SHA-256 摘要缓存起来。之后的连接,服务器通过缓存直接验证,不走完整流程,速度很快。这是「caching」这个词的由来。
- 完整路径(缓存未命中):首次认证、服务器刚重启、或者
FLUSH PRIVILEGES清空缓存后,需要走完整的 RSA 加密流程。
完整路径这里有个关键分支:要分「加密连接」和「非加密连接」两种情况。
- 如果客户端和服务器之间是 TLS 加密连接:密码可以通过加密通道直接发送,安全。
- 如果是 明文连接(没开 TLS):
caching_sha2_password要求用服务器公钥加密密码后再发。这需要客户端拿到服务器的 RSA 公钥。
而在明文连接下,客户端拿公钥的方式有两种,靠一个参数控制:--get-server-public-key。如果没指定这个参数,客户端会拒绝继续认证,报错退出——这又是另一个常见的「连不上」原因。
把这两点合起来看,你就能解释清楚那个经典现象了:同一个账号,命令行客户端能连是因为它默认带了 --get-server-public-key;而你的老程序连不上,是因为它的驱动不支持这个参数或者根本不认识 SHA-256 流程。
三、先诊断:查清楚现状再动手
动手改之前,先把当前状态摸清楚。连接上 MySQL 后执行:
-- 看默认认证插件是什么
SELECT @@default_authentication_plugin;
-- MySQL 8.0.27+ 改用这个变量名
SELECT @@authentication_policy;
-- 列出所有用户的认证方式
SELECT user, host, plugin
FROM mysql.user
ORDER BY plugin, user;典型输出里,你会看到一批用户是 caching_sha2_password,可能还有一些是 mysql_native_password(比如从 5.7 升级上来的老用户,或者你手动改过的)。注意 auth_socket 是 Linux 上 root 用本地 socket 免密登录的方式,不要乱动它,那是故意设计的。
然后确认你的客户端到底支不支持新插件:
-- 查看客户端版本和能力
SELECT VERSION();
SHOW PLUGINS;客户端这边,最直接的是看 mysql 命令行工具的版本,以及你的应用用的驱动版本(PHP 的 mysqli/PDO_MySQL、Python 的 PyMySQL/mysqlclient 等)。记住一个基线:所有客户端/驱动都应该升级到 8.0 或更新的版本,才能真正原生支持 caching_sha2_password。
四、迁移方案一:升级客户端(首选,治本)
最正确的做法是升级客户端而不是降级服务器。安全方向永远应该往前看。具体到不同语言:
- PHP:使用 PHP 7.4+ 并确保
mysqlnd驱动启用。PHP 7.4 及以上的 mysqlnd 原生支持caching_sha2_password。PDO_MySQL从 PHP 7.4 起也支持。如果你还在用 PHP 5.x 或者老版本的libmysqlclient编译的扩展,那就必须先升级 PHP。 - Python:
mysqlclient需要 1.4.x 以上版本并链接 MariaDB Connector 或 MySQL 8 的库;PyMySQL需要 0.9.x 以上;mysql-connector-python从 8.0 起原生支持。 - Java:
mysql-connector-java需要 8.0.x,5.1.x 系列不支持。 - 命令行工具:升级
mysql-client到 8.0。
升级完成后,连接时如果走明文连接,要记得加 --get-server-public-key(命令行)或在连接串里配 getServerPublicKey=true(JDBC)。更推荐的做法是直接启用 TLS 连接,让完整路径走加密通道,避免公钥获取的麻烦。
# 命令行连接时探测
mysql -u appuser -p -h db.example.com --get-server-public-key
# 或者强制走 SSL
mysql -u appuser -p -h db.example.com --ssl-mode=REQUIRED五、迁移方案二:逐个用户切换认证方式
如果客户端短期内升不了级(比如老系统硬依赖某个旧版本的库),可以针对性地把某些用户改回 mysql_native_password,而保持数据库默认还是 caching_sha2_password。这样既兼容了老客户端,又不用把整台服务器降级。
-- 针对单个用户切换(MySQL 8.0 推荐写法)
ALTER USER 'appuser'@'10.0.0.%'
IDENTIFIED WITH mysql_native_password BY '你的强密码';
-- 查看确认
SELECT user, host, plugin FROM mysql.user WHERE user='appuser';
-- 让权限缓存立即生效(一般 ALTER USER 会自动生效,这里保险起见)
FLUSH PRIVILEGES;有几个重要的注意点:
- 语法随版本变化。MySQL 8.0.14 及以后,
GRANT ... IDENTIFIED BY不再用于改密码,必须用ALTER USER。IDENTIFIED WITH ... BY是正确写法。 - 改的是「该用户以后用什么插件认证」,不是改密码哈希算法回退。密码本身会按新插件重新计算存储,所以老密码仍能用(前提是你写的是同一个密码)。
- 不要全局降级默认插件。把
default_authentication_plugin改回mysql_native_password是很多人图省事的做法,但这等于把整台服务器的安全基线拉回到 SHA-1,新建的所有用户都会用弱插件。仅在过渡期内、且明确知道风险时才用,并且要记录待办跟进。
六、方案三:明文连接下用 RSA 公钥的正确配法
如果因为环境限制没法上 TLS,又必须用 caching_sha2_password,那就得走 RSA 公钥路线。这里的原则是宁可让客户端每次都向服务器要公钥,也不要把公钥硬编码在客户端。
服务器端确认公钥文件存在:
SHOW VARIABLES LIKE 'caching_sha2_password_public_key_path';
SHOW VARIABLES LIKE 'caching_sha2_password_auto_generate_rsa_keys';
-- 手动获取服务器公钥(客户端侧执行)
-- 或在应用连接串里设置 getServerPublicKey=true对 PHP 的 PDO 来说,可以这样连接(伪代码示意):
$pdo = new PDO(
"mysql:host=db.example.com;dbname=mydb",
"appuser", "password",
[PDO::MYSQL_ATTR_SSL_CA => "/etc/ssl/certs/ca.pem"] // 推荐:直接上 TLS
);更省心的做法还是干脆把连接升级成 TLS。MySQL 8 默认就会生成自签名证书,把服务端 CA 拷给客户端验证即可,这样 caching_sha2_password 的完整路径就自动走加密通道了,不需要处理 getServerPublicKey 这个参数。
七、验证与排查清单
改完之后,用这套清单逐项确认:
- 服务器侧:
SELECT user, host, plugin FROM mysql.user;确认目标用户的插件是你期望的。 - 客户端侧:用实际应用去连(不要只用命令行客户端),因为命令行默认行为和驱动的默认行为经常不一样。
- 明文连接场景:故意用不带
--get-server-public-key的明文连接试一次,如果报错,说明这个路径需要显式处理,要确保应用连接串配置正确。 - 连接池:应用如果用了连接池,改认证插件后要重启应用让连接池丢弃旧连接,否则池里可能残留用旧插件建立的连接,行为不一致。
- 监控脚本:别忘了那些不起眼的定时脚本、备份脚本、Prometheus 的 mysqld_exporter,它们用的也是独立的客户端库,同样需要检查。
八、给个人站长的实操建议
- 升级 MySQL 前先盘客户端。在动手升级之前,列出所有连数据库的东西:应用的驱动版本、命令行工具、备份脚本、监控 exporter、连上去的 GUI 工具。提前确认它们是否支持 caching_sha2_password。
- 优先升级客户端,而不是降级服务器。这是唯一不牺牲安全性的路线。老组件如果实在动不了,再针对性地给个别用户开 mysql_native_password 作为过渡。
- 能上 TLS 就上 TLS。MySQL 8 自带证书生成,启用 TLS 不但让缓存认证的完整路径更顺,也顺带保护了数据库流量。这是性价比很高的一步。
- 把「降级默认插件」当技术债记账。如果迫不得已改了
default_authentication_plugin,就在运维笔记里记一笔,排个期把客户端升级到位后改回来。 - 改完必须实测应用连接。不要只看命令行能连就以为万事大吉。用真实应用的连接路径验证,才是唯一可信的标准。
认证插件换代这件事,本质上是一次安全与兼容性的取舍。MySQL 8 选择往前迈了一步,代价就是逼着我们把生态里的旧组件推着一起升级。理解 caching_sha2_password 的两条认证路径和它的缓存机制,你就能在遇到各种「连不上」的报错时,不再靠猜,而是顺着流程一层层定位到真正的原因。这比记住一个「改回 mysql_native_password」的偏方,有价值得多。