如果你最近正好在折腾 MySQL 8.0,尤其是从 5.7 往上迁移,或者干脆用 Docker 拉了一个mysql:8.0镜像准备跑业务,大概率会在连接阶段撞上一面墙——报错信息是一串英文:Client does not support authentication protocol requested by server; consider upgrading MySQL client。
这个报错我在不同的环境里遇到不下五次,每次都是连接工具或驱动版本没跟上服务器认证方式的升级。很多朋友第一反应是去改密码,或者重装 MySQL,但真正的原因并不是密码错了,而是认证插件不匹配。这篇文章就把这个报错发生的原因、排查思路和所有可行的解决办法一次性说透,适合正在迁移 MySQL 8.0 的开发者、运维,以及用 Docker 部署 MySQL 后卡在连接环节的新手。
1. 先搞清楚报错到底在骂谁:认证插件不兼容
1.1 把报错翻译成人话
报错原文拆开看并不复杂:
Client does not support...:客户端(连接工具、驱动、命令行客户端)不支持服务器要求的认证协议;authentication protocol requested by server:服务器要求的是某一种认证协议;consider upgrading MySQL client:建议升级 MySQL 客户端。
本质上,这是 MySQL 在握手阶段就拒绝了你。服务端说“我要用caching_sha2_password这种认证方式和你对话”,客户端这边回了一句“我不会这个”。于是连接直接被掐断,密码还没来得及校验,所以你会看到“Access denied”之前的这个协议报错。
有些工具显示方式不同,比如报Authentication plugin 'caching_sha2_password' cannot be loaded,或者是Public Key Retrieval is not allowed,这些都是同一个大问题在不同客户端上的不同表现。只要看到“authentication protocol”“authentication plugin”“public key retrieval”这些关键词,基本都能归到一类。
1.2 为什么 MySQL 8.0 要换认证方式
MySQL 5.7 及更早版本默认用的是mysql_native_password认证插件。这个插件在当年够用,但实现方式比较简单:客户端和服务端基于 SHA1 做一个挑战-响应验证,服务器上存储的也是密码的 SHA1 哈希。问题在于,这种哈希不能加盐,一旦攻击者拿到mysql.user系统表的数据,就可以离线暴力破解密码。
MySQL 8.0 把默认认证插件换成了caching_sha2_password。这个名字看着长,核心变化有两个:
- 密码哈希基于 SHA256,安全性高很多;
- 首次认证时,要么走 TLS 加密通道,要么通过 RSA 公钥把密码加密后再发给服务器,避免密码在网络传输中裸奔。
同时它有一个缓存机制:某个用户第一次完成认证后,服务端会把认证结果缓存起来,后续短时间内的连接就不需要重复走完整的公钥加密流程,对性能影响很小。
打个比方:服务器换了一把新锁,你还拿着旧钥匙去开,当然开不了。不是密码的问题,是你们双方约定的“开锁协议”根本没对上。
1.3 最容易触发这个报错的场景
根据我的经验,以下场景最容易撞上:
- 老版本 GUI 工具连新库:Navicat 12 及更早、SQLyog 老版本,这类工具对 MySQL 8.0 的新认证支持都很差;
- 历史项目依赖太旧:Java 项目里躺着
mysql-connector-java 5.x,Python 项目用老版本 PyMySQL; - Docker 部署后本地连接:
mysql:8.0镜像初始化时,root 用户默认用的就是caching_sha2_password,如果你本机连接工具是老版本,立刻报错; - 公司内部封闭环境:工具和驱动都由统一分发,版本常年不升级,碰到 MySQL 8.0 集体“阵亡”。
2. 动手之前先摸清现场:三步定位问题来源
2.1 确认服务器端版本和用户认证方式
出现报错后,先不要急着改配置。第一步是确认服务器端当前用户到底要求什么认证插件。
如果命令行下还能连上(比如用服务器本地的 mysql 客户端,或者你本来就有另一个能连上的账号),执行:
SELECT VERSION(); SELECT user, host, plugin, password_expired, account_locked FROM mysql.user WHERE user = 'root';重点看plugin列。如果是caching_sha2_password,那就证实了服务端要求新协议;如果是mysql_native_password,说明服务端本身还允许老协议,那问题可能出在别的地方。
顺手再查一下全局默认配置:
SHOW VARIABLES LIKE 'default_authentication_plugin';注意:MySQL 8.4 之后这个变量已经被移除了,8.0 系列还能查到。如果你用的是 8.4,要看的是authentication_policy变量。
2.2 确认客户端和驱动的版本
这一步不能省。我习惯把连接链路里每一环都列成一张表,填完之后问题基本就锁定了:
| 对象 | 版本 | 是否支持默认认证 |
|---|---|---|
| MySQL Server | 8.0.x | 是(它自己就是新协议) |
| mysql 命令行客户端 | 与服务器同版本 | 是 |
| Navicat | 12.x | 否 |
| JDBC 驱动 | 5.1.x | 否 |
| PyMySQL | 0.9 | 需要配合 cryptography 包 |
| PHP mysqlnd | 7.2.4 以下 | 否 |
每一栏填完,你会看到是哪一环卡住了。大多数情况下,报错都是“服务器是新的,客户端工具或驱动是老的”这个组合。
2.3 决定改服务器还是改客户端
搞清楚“谁不支持谁”之后,就到了决策环节。我的判断标准是这样的:
- 如果客户端工具是个人电脑上安装的,优先升级工具,这是最省事也最安全的路;
- 如果客户端驱动是历史项目里带的,先评估升级成本,升级不了再考虑改服务器端;
- 如果数据库是生产环境,不建议为了兼容老工具而全局降级认证插件;
- 如果只是临时排查,可以把某个用户单独改回老插件,但要在工单里记一笔,后续安排升级。
提示:能改客户端就不要先改服务器。把数据库认证方式整体降级,所有新建用户都会默认走老协议,后续安全审计会比较被动。
3. 最直接的解法:把用户认证方式调回 mysql_native_password
3.1 对已有用户单独修改
如果客户端一时半会儿升不了级,最直接的办法就是把用户认证插件单独改回mysql_native_password。
登录 MySQL 后执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的新密码'; ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的新密码'; FLUSH PRIVILEGES;这里有几个细节要特别提醒:
@'localhost'和@'%'不要漏。之前有人只改了localhost,远程连接还是报错,因为他的应用连的账号匹配的是'%'那条记录;- 这条命令在改插件的同时也重设了密码,所以执行完要记得同步修改应用里的数据库账号密码;
FLUSH PRIVILEGES在多数场景下不是必须的(ALTER USER已经生效),但执行一下不会有害,尤其当你用了--skip-grant-tables之类的方式恢复过权限表时。
改完再确认一次:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';看到plugin变为mysql_native_password,再用原来的工具重连,基本就通了。
3.2 新建用户时直接指定插件
如果你在短时间内没法升级客户端,又需要为某个应用创建免密或固定密码的账号,可以让新用户从创建那一刻就直接使用老插件:
CREATE USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY 'apppass'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'appuser'@'%';这样新建用户默认不受caching_sha2_password影响,应用侧什么都不用改。
3.3 全局兜底:修改默认认证插件(含 Docker 场景)
如果希望整个实例的新建用户都默认走mysql_native_password,可以修改配置文件:
[mysqld] default_authentication_plugin=mysql_native_password保存后重启 MySQL 服务生效。改之前记得备份原配置文件。
如果你是 Docker 部署的 MySQL,操作路径稍有不同。比如启动容器时把配置目录挂载出来:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPassword \ -v /opt/mysql/conf.d:/etc/mysql/conf.d \ mysql:8.0宿主机上新建/opt/mysql/conf.d/my.cnf,写上:
[mysqld] default_authentication_plugin=mysql_native_password然后重启容器:
docker restart mysql8如果要修改已有用户的插件,直接用docker exec进入容器执行 SQL:
docker exec -it mysql8 mysql -uroot -p注意:Docker 里通过
MYSQL_ROOT_PASSWORD环境变量初始化的 root 用户,默认认证插件同样是caching_sha2_password,不存在“容器里就是老协议”的例外,所以该改还是要改。
4. 不想动服务器认证,那就把客户端升级到位
4.1 GUI 工具与命令行客户端
最省心的办法其实是升级客户端,而不是去迁就服务器。
- Navicat 15 及之后版本对 MySQL 8.0 的新认证支持比较好,12 及更早版本就经常撞协议报错;
- MySQL Workbench 8.0 系列本身没这个问题;
- HeidiSQL、DBeaver 近年版本也基本兼容。
升级工具有个容易忽略的操作:升级后要把原来的连接配置删掉重建,因为工具内部会缓存认证插件相关的信息,不清理的话可能继续报错。
临时验证可以用服务器自带的命令行客户端:
mysql -h 127.0.0.1 -P 3306 -uroot -p命令行走通,说明服务端没问题,问题只在具体工具或驱动上。
4.2 Java/JDBC:重点在连接串参数
Java 项目连 MySQL 8.0 有两个常见坑。第一个是驱动版本太老,先升级mysql-connector-java到 8.x。第二个是升级之后可能还会报:
Public Key Retrieval is not allowed这是因为caching_sha2_password在非 TLS 连接下,需要客户端向服务器请求 RSA 公钥,然后客户端用公钥把密码加密再发给服务器。出于安全考虑,JDBC 驱动默认不允许自动获取公钥,必须显式开启。
连接串里加上这两个参数:
jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai简单解释一下:
allowPublicKeyRetrieval=true:允许客户端向服务器获取 RSA 公钥,解决公钥获取限制的问题;useSSL=false:因为如果你不开 SSL,就走公钥交换流程;如果连接本身已经配置了 SSL 证书,这条可以不加,但公钥参数还是要酌情保留;serverTimezone:MySQL 8.0 对时区处理比 5.x 严格,很多老项目在升级时都会顺带遇到时区转换报错,加上这个参数能少踩一个坑。
4.3 Python/PHP/Go 等语言驱动的匹配版本
语言驱动的兼容性可以按下面这个表快速核对:
| 语言/驱动 | 兼容版本提示 | 额外注意事项 |
|---|---|---|
| PyMySQL | 1.0 以上版本稳定 | 连接报 RSA 相关错误时,执行pip install cryptography |
| mysql-connector-python | 8.x 原生支持 | 连接参数里可以显式指定auth_plugin |
| PHP mysqlnd | PHP 7.2.4+ | 老 PHP 项目升级要连带评估应用兼容性 |
| Go go-sql-driver/mysql | v1.5+ | 新版驱动一般没问题 |
| MySQL ODBC | ODBC 8.0+ | 需要配好 Microsoft Visual C++ 2015-2022 运行库 |
升级驱动之后,一定要重启应用,清空连接池里的旧连接,否则应用可能还在用启动时加载的旧驱动类,表现上就是“升级了但还是报错”。
5. 实测复盘:一次完整的排错链路
5.1 场景还原
我拿一个很典型的场景走一遍完整流程:Windows 开发机上用 Navicat 12 连接 Docker 里的mysql:8.0容器,端口映射为 3306。
第一步,Navicat 连接报Client does not support authentication protocol requested by server。先不搜答案,先用命令行确认服务端是活的:
mysql -h 127.0.0.1 -P 3306 -uroot -p命令行走通,说明容器正常,问题出在认证协议层。
第二步,进入容器查用户认证插件:
docker exec -it mysql8 mysql -uroot -pSELECT user, host, plugin FROM mysql.user WHERE user = 'root';结果里 root 的记录是caching_sha2_password。
第三步,判断方向。Navicat 12 是公司统一安装的老版本,没法立刻升级,那就临时改用户插件:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'NewPass123!'; FLUSH PRIVILEGES;第四步,Navicat 重新连接,成功。
这里有一个很关键的判断:连接走的是哪条 host 记录。查询结果里如果存在root@'%',远程连接基本都会匹配到它,所以只改localhost是没用的。
5.2 不同报错形态的快速对照
不同客户端对同一个底层问题的表达方式不一样,整理成表方便对照:
| 报错原文/关键词 | 根因 | 解法方向 |
|---|---|---|
| Client does not support authentication protocol requested by server | 客户端不认 caching_sha2_password | 升级客户端或改用户插件为 native |
| Authentication plugin 'caching_sha2_password' cannot be loaded | 驱动版本太老,加载不了新插件 | 升级驱动 |
| Public Key Retrieval is not allowed | JDBC 在非 TLS 下没开公钥获取 | 连接串加 allowPublicKeyRetrieval=true |
| Access denied for user 'root'@'host' (using password: YES) | 密码错误或 host 匹配错误 | 核对账号 host 和密码,必要时 ALTER USER 重设 |
| 连接后提示密码过期 | password_expired 为 Y | 重设密码或设置 PASSWORD EXPIRE NEVER |
5.3 改完连接仍失败的三个隐藏坑
第一种坑是连接池缓存。应用启动时建立的连接还在用旧握手状态,服务端那边可能检测到协议不匹配直接断开,表现为“改了插件还是间歇性报错”。处理方式是重启应用,或者清空连接池。
第二种坑是 host 记录没改对。判断自己当前到底走了哪条 host 记录,用这条 SQL:
SELECT CURRENT_USER();比如结果是root@'%',那你要改的就不是root@'localhost',而是root@'%'那条。
第三种坑是密码策略限制。改插件并重设密码时,如果开了validate_password组件,新密码必须满足长度、大小写、数字、特殊字符的要求。测试环境可以临时放宽策略,但生产环境不建议这么做。
6. 别只盯插件:版本趋势与长期规划
6.1 MySQL 8.4 之后,mysql_native_password 开始退场
8.0 系列还能靠default_authentication_plugin=mysql_native_password全局兜底,但从 MySQL 8.4 开始,官方在默认配置里禁用了mysql_native_password,旧参数也被authentication_policy取代。如果确实还想启用老插件,需要显式开启mysql_native_password=ON。
这说明官方态度已经很明确:所有客户端最终都要迁到caching_sha2_password。所以这次通过改回 native 插件解决问题,只能算“救急”,不是长期方案。有条件的话,还是把客户端驱动升级排在后续工作计划里。
MySQL 8.4 里查看当前认证策略:
SHOW VARIABLES LIKE 'authentication_policy';6.2 生产环境的认证兼容改造建议
最后给生产环境一个可落地的改造思路,这是我实际处理过多次迁移后总结出来的顺序:
先做一次认证插件盘点:
SELECT user, host, plugin, COUNT(*) FROM mysql.user GROUP BY plugin;然后按业务优先级逐个迁移用户插件。每改完一个用户,立刻用对应业务链路的测试脚本连接一次。整个过程建议放在低峰期,避免影响线上。
有一点我要特别强调:不要为了兼容一个老工具,就让整个生产库全局降级认证插件。这个决定当时看着省事,后续安全评审时会被反复追问。正确做法是“单用户兼容、整体升级客户端”。
另外,不管用哪种认证方式,都不要把明文密码留在配置文件或 Docker 环境变量里。Docker Compose 可以配合env_file或 secret 机制管理密码,这和本次报错没有直接关系,但同类排错时很容易顺手踩到密码泄露风险,值得一起改掉。
这个问题我前前后后处理过很多次。早期图省事,上来就改回mysql_native_password,后来在安全评审时被问得难受。现在的习惯是先查客户端版本,能升级就升级,只有在老工具实在不能动时,才去单独改某个用户的认证插件。最后再分享一个小技巧:在测试库里同时建一个 native 用户和一个caching_sha2_password用户,新工具连新的、老工具连旧的,以后排查类似的协议问题能节省大量来回确认的时间。