相信不少朋友第一次在项目里切换到 MySQL 8.0 时,都被这条报错狠狠折磨过:
Client does not support authentication protocol requested by server; consider upgrading MySQL client短的一行英文,信息量却很大。明明数据库装好了、账号密码都对、端口也通,可客户端就是连不上去。更让人头疼的是,网上搜到的解决方案五花八门,有让你改my.cnf的、有让你重装驱动的、还有让你降级 MySQL 的,看了一圈更乱了。
这篇文章不谈虚的,咱们把这条报错彻底讲透。从 MySQL 8.0 为什么要改认证协议讲起,到不同场景下到底该用哪种解法,再到实际操作时最容易踩的坑,全部过一遍。不管你用的是 Navicat、PHP、Python、Java JDBC 还是 Docker 里的 MySQL 容器,看完都能找到对应的处理思路。
1. 问题全貌与根因剖析
这条报错的英文全称是Client does not support authentication protocol requested by server; consider upgrading MySQL client,翻译过来就是:客户端不支持服务器要求的认证协议,请考虑升级你的 MySQL 客户端。
报错本质不是密码错误,也不是网络不通,而是"客户端和服务端在认证方式上谈不拢"。
1.1 一切源于 MySQL 8.0 的默认认证插件变更
MySQL 5.7 及更早版本,默认的认证插件是mysql_native_password。这个插件从 MySQL 4.1 时代就开始用,二十年下来,几乎所有客户端、驱动、GUI 工具都兼容它,可以说是"最大公约数"。
到了 MySQL 8.0,官方把默认认证插件换成了caching_sha2_password。名字虽然拗口,但它的设计初衷是好的:比老插件具备更强的密码安全性,基于 SHA-256 的哈希算法,配合服务端的缓存机制,能有效避免密码在传输过程中的风险。
问题就出在这里。你数据库版本升到了 8.0,默认按新插件创建用户,但你的客户端还是按老规矩用mysql_native_password来打招呼。服务端说"我要用 caching_sha2_password 和你认证",客户端说"我只懂 mysql_native_password",两边一对话就崩了,于是抛出这条报错。
1.2 哪些客户端会踩中这个坑
搞清楚这个根因后,哪些场景容易出问题就很好判断了。只要客户端的"语言"版本对应不上新插件的,都会中招:
- 老版本 Navicat:特别是 11.x、12.x 时代(确切说 16.x 之前的一些版本)对 MySQL 8.0 的 caching_sha2_password 支持不完整。
- PHP 的 mysql 扩展 / mysqli 扩展版本过低:PHP 5.x、7.0 时代的老驱动不认识新插件。
- Python 的 pymysql / MySQLdb:老版本库在初始化连接时没有按新插件处理密码报文。
- Java 的 JDBC 驱动:mysql-connector-java 5.1.x 系列基本无力支撑,8.0.x 系列才完整支持。
- 命令行 mysql 客户端版本过旧:比如系统自带的是 MySQL 5.5/5.6 时代的客户端,去连 8.0 服务端,大概率报错。
1.3 顺带理清:到底谁在"坚持老协议"
这里有一个容易混淆的点。MySQL 8.0 服务端并不是只支持新插件,它依然保留了对mysql_native_password的兼容能力。换句话说,服务端是"能说新话也能说老话"的,只是默认情况下创建用户时用的是新插件。问题更多出在客户端那头——客户端太老了,只会说老话,而服务端默认行为是新话,两者就错位了。
所以解决思路不外乎两个方向:要么让服务端"迁就"客户端,把用户的认证插件改回老的mysql_native_password;要么让客户端"跟上"服务端,升级组件、驱动或连接器,使其支持caching_sha2_password。
在我实际处理过的项目里,这两种思路都有各自的适用场景,下面逐个拆开讲。
2. 两种解决方向与选型考量
解决这条报错,大的方向就两条:修改服务端用户认证方式、升级客户端组件版本。听起来简单,但选哪个方向,得结合你的项目实际情况来判断,选错了后面会埋雷。
2.1 方向一:修改服务端用户认证插件(快速见效)
这种做法的核心命令就一条:
ALTER USER 'username'@'host' IDENTIFIED WITH mysql_native_password BY 'password';把用户的认证方式从caching_sha2_password改成mysql_native_password,客户端的老驱动立刻就能连上了。我见过很多团队遇到报错后,直接在命令行执行这条,马上解决问题,效率确实高。
但请注意,这个操作有一个需要权衡的地方:mysql_native_password的安全性比caching_sha2_password低一档。它虽然也做哈希,但算法相对旧,在暴力破解面前防护能力弱一些。如果你的数据库直接暴露在公网,或者对安全等级有硬性要求(比如等保测评、金融行业规范),这种"降级"操作要慎重考虑,尽量别用。
还有一点,如果你用的是 MySQL 8.0 默认配置,my.cnf里并没有显式写default_authentication_plugin,那么修改用户后新用户依然默认按新插件创建,你得记得对每个需要的用户都执行一遍修改,或者干脆在配置里写死默认插件(后面会细说)。
2.2 方向二:升级客户端组件(治本)
第二种思路是让客户端去适配新协议。具体来说:
- Navicat:升级到 16.x 或更新版本,完整支持 MySQL 8.0 认证。
- Java JDBC:把
mysql-connector-java换到 8.0.x 以上,新版驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver,同时 URL 里通常要加allowPublicKeyRetrieval=true&useSSL=false等参数。 - PHP 环境:PDO 的
mysqlnd驱动在 PHP 7.2 之后就支持了 caching_sha2_password,升级即可。 - Python:pymysql 在 0.9.3 之后的版本支持新认证,升级
pip install --upgrade pymysql就能解决。 - 命令行客户端:升级到 8.0 对应的 mysql-client 版本。
治本方案的好处是不会有安全降级,但坏处也很明显:升级组件行为可能会影响整个项目环境,遇到老系统(比如某个上了年纪的 PHP 项目跑在 CentOS 6 上)不一定能轻轻松松完成升级。
2.3 我的选型建议
简单总结一下我的经验:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 本地开发环境、内网测试环境 | 修改认证插件 | 快、省事,安全风险可控 |
| 生产环境、公网暴露 | 升级客户端组件 | 保持高安全等级,不降级协议 |
| 老项目无法升级依赖 | 修改认证插件 | 等保不严格时,兼容性优先 |
| Docker 容器内的 MySQL | 根据客户端情况二选一 | 容器操作要额外注意,见下文第三节 |
这里还想提一句:不要一上来就全局改my.cnf。除非你的客户端生态已经固定死且无法升级(比如公司内部统一用老版 Navicat),否则优先"改单个用户"而不是"改全局默认",影响面小得多。
3. 实操过程与核心环节实现
方向定了,接下来手把手走一遍。我分别给出命令行直接操作和 Docker 容器两种场景的完整流程,顺带把 Navicat、Python 连接时的关键配置也带出来。
3.1 命令行直接修改用户认证插件
第一步,用 root 或其他有权限的账号进入 MySQL:
mysql -uroot -p输入密码进入后,先确认当前用户的认证插件状态。注意 MySQL 8.0 的用户信息存在mysql.user表里,插件字段在plugin列:
SELECT user, host, plugin FROM mysql.user WHERE user = 'youruser';输出里你会看到类似caching_sha2_password的记录,这就对应了报错来源。
第二步,执行修改:
ALTER USER 'youruser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';这里有两个细节必须说清楚。
细节一:host到底写什么。如果你的程序是远程连接,localhost就要改成%或者对应的 IP 段。我见过太多人在这里卡壳——明明本地用 root 执行成功了,程序远程连接还是报错,就是因为本地用户和远程用户是两个不同的账户记录。MySQL 的账号由user + host共同决定,别只盯着用户名看。
细节二:IDENTIFIED WITH和IDENTIFIED BY的关系。上面的写法等价于"指定插件方式并设置密码为指定串"。如果只是想改插件、保留现有密码,老版本里有过一种写法:
ALTER USER 'youruser'@'localhost' IDENTIFIED WITH mysql_native_password;但实际测试中发现,部分 MySQL 8.0 版本对这种省略密码的写法支持不友好,可能会报语法错误。稳妥起见,还是显式带上BY 'password',明确告诉数据库"插件换成老的,密码用这个"。
第三步,刷新权限并验证:
FLUSH PRIVILEGES;刷新不是必须的(ALTER USER 隐含生效),但养成习惯总没坏处。验证连接时,退出后重新用你的客户端工具连一次。如果之前的报错消失,说明问题解决。
3.2 在 MySQL 8.0 下创建新用户时直接指定老插件
如果你刚装好 MySQL 8.0,还没建用户,那更简单,创建用户时直接指定插件就行:
CREATE USER 'newuser'@'%' IDENTIFIED WITH mysql_native_password BY 'password123'; GRANT ALL PRIVILEGES ON yourdb.* TO 'newuser'@'%'; FLUSH PRIVILEGES;这个操作里的'%'表示允许从任意主机连接。如果只想允许本机,用'localhost';如果只想允许某个固定 IP,写具体地址。
需要注意,MySQL 8.0 安装完成后,root 用户的 host 默认是localhost。有些教程让你把 root 改成%,我不推荐这么干——root 是超级账号,远程登录风险太高。正确做法是建一个业务专用账号,按需授权,root 留作本地管理。
3.3 Docker 容器里的特殊处理
很多人喜欢用 Docker 跑 MySQL 8.0,比如执行这样的命令快速建容器:
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0如果你在宿主机上用命令行客户端连接,遇到同样的认证报错,处理方式还不太一样。容器内的 MySQL 是全新安装的,默认用户 root 用的是新认证插件,此时你有两个选择。
选择一:进容器执行 SQL。
docker exec -it mysql8 mysql -uroot -p进入 MySQL 命令行后,执行:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root123'; FLUSH PRIVILEGES;但这里有个坑:容器环境下,root 的 host 可能不是%,而是localhost或127.0.0.1。取决于你的启动命令和官方镜像初始化脚本的行为。不确定时先查:
SELECT user, host, plugin FROM mysql.user;看到root对应的确切 host 再改,别盲目猜。
选择二:启动容器时通过环境变量指定认证插件。
MySQL 官方 Docker 镜像很早就支持MYSQL_ROOT_PASSWORD、MYSQL_DATABASE等环境变量,但认证插件相关的是--default-authentication-plugin这个 mysqld 参数。在 Docker 里可以通过命令追加的方式传递:
docker run -d --name mysql8 -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ mysql:8.0 --default-authentication-plugin=mysql_native_password注意参数要放在镜像名后面,因为它实际上是传给了 MySQL 容器的启动命令。这样容器初次初始化时创建的用户就都使用老插件。这个方法适合"我再也不想折腾认证问题了"的场景,但同样有安全降级的顾虑,不建议在生产环境无脑用。
3.4 Navicat 场景的处理细节
如果不想改服务端,而是想通过升级 Navicat 解决,建议升级到 16.x 版本。验证是否支持的简易方法:连接时如果报错信息消失、正常出现输密码的交互,说明认证兼容。如果还是报错,再退回用 ALTER USER 方案。
还有个细节:Navicat 连接 MySQL 8.0 时,如果连接参数里"允许保持连接"或"使用 SSL"设置跟服务端不匹配,也可能出现其他异常。顺手把连接属性里useSSL的开关状态换一下再试,排除干扰项。
3.5 Python / Node.js 程序的配置要点
Python 场景:
pymysql 老版本不支持 caching_sha2_password,最简单的修复是升级到新版:
pip install --upgrade pymysql如果你无法升级(比如受限于项目的依赖锁定),那么在数据库端把对应用户改成 mysql_native_password,并且连接时通常不需要额外参数,因为老插件下握手是明文的。使用 MySQL 官方的mysql-connector-python,在连接字串里加上auth_plugin='mysql_native_password'也行,但这个选项会随着 MySQL 版本更新被逐步弃用,建议优先考虑升级库版本。
Node.js 场景:
mysql2驱动是目前支持 MySQL 8.0 认证最稳的选择。mysql(也就是mysqljs/mysql)老版本对新协议支持不好,代码里会报同样的错误。把连接方式改成:
const mysql = require('mysql2/promise'); const conn = await mysql.createConnection({ host: 'localhost', user: 'youruser', password: 'yourpassword', database: 'yourdb' });mysql2底层实现了 caching_sha2_password 的握手流程,所以无需改数据库端。
3.6 补充:通过配置全局默认插件
还有一种一了百了的改法,修改 MySQL 配置文件my.cnf,在[mysqld]段下加一行:
default_authentication_plugin=mysql_native_password然后重启 MySQL 服务。
这样新建的所有用户(包括之后 CREATE USER 的)都会默认使用老认证插件。这条命令只影响"新创建的用户",已存在的用户不会被改变,所以早期踩过坑的用户仍然要用 ALTER USER 单独修。
之所以不推荐上来就改全局,是因为它影响面太大,而且把 MySQL 8.0 的安全特性人为关闭了。如果你团队里有人专门负责安全,这个操作大概率会被打回。
4. 常见问题与排查技巧实录
处理和这个报错相关的线上问题时,我积累了一些排查技巧,也发现了一些文档里不太会写清楚的坑。整理出来,希望帮你少走弯路。
4.1 问题速查表
| 问题现象 | 可能原因 | 处理办法 |
|---|---|---|
| 客户端连不上,报 authentication protocol 错误 | 客户端太老,不认识 caching_sha2_password | 升客户端,或 ALTER USER 改插件 |
| root 本地可连,远程程序连不上 | 远程用户的 host 和本地不是同一个 | 查 mysql.user 确认 host,按需创建远程账号 |
| 改了 ALTER USER 但程序还是报错 | 密码缓存 / 连接池老连接未释放 | 重启程序或连接池,确认没有中间代理缓存 |
| Docker 容器改完用户后重启失效 | 容器重建 / 数据卷未持久化 | 用 volume 持久化数据,或启动命令配插件 |
| 修改 mysql.user 表直接 UPDATE plugin 报错 | MySQL 8.0 不允许直接 UPDATE plugin 字段 | 用 ALTER USER 语法,不要手动 UPDATE |
| 新版 Navicat 连接报 "Authentication plugin 'caching_sha2_password' cannot be loaded" | 某些 Linux 版本图形工具依赖缺失 | 更新工具,或改服务端用户插件 |
| JDBC 连接时报 Public Key Retrieval 错误 | 新版驱动要求 fetch 服务端公钥 | URL 加 allowPublicKeyRetrieval=true 并配合 useSSL=false |
4.2 一个容易出问题的细节:mysql_native_password 的密码兼容
ALTER USER 'user'@'host' IDENTIFIED WITH mysql_native_password BY 'password'这个操作,在 MySQL 8.0 里是支持的。但有个冷知识:老插件保存密码哈希时用的是 41 字节的*开头哈希串。如果你从旧版本数据库导出用户,再导入 8.0,可能会碰到哈希格式不兼容的情况。这种场景一般出现在"数据库跨大版本迁移"时,解决办法是用SET PASSWORD重新生成哈希,不要把老库的 user 表直接搬到新库。
4.3 排查思路的核心要点
不管遇到什么客户端,先做三件事:
第一步,进数据库查用户插件:
SELECT user, host, plugin FROM mysql.user WHERE user = '你的用户名';第二步,确认客户端的连接协议版本。命令行可以用:
mysql --version这一步能快速判断是客户端过老还是服务端配置问题。
第三步,用排除法试连。比如 JDBC 场景,先在本机用 mysql 命令行客户端连一次,如果命令行能连、程序连不上,说明问题大概率出在驱动的 URL 参数或版本上,而不是数据库端。
我测试过最常见的组合:Ubuntu 上自带的 mysql-client 8.0 连 MySQL 8.0 服务端完全没问题;但如果系统里装的是 mysql-client 5.7,哪怕服务端是 8.0,也会大概率报错。所以命令行客户端本身的版本升级也值得留意。
4.4 针对 caching_sha2_password 的加密传输补充说明
最后再补充一个关于 caching_sha2_password 的细节。这个插件在 TCP 连接上,第一次握手时需要额外的 RSA 公钥交换过程。如果客户端不支持或者无法完成这个交换,同样会导致连接失败。表现出的报错可能是Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection。
这种场景下,有两个可用的处理办法:
- 在 JDBC 连接字串里加
allowPublicKeyRetrieval=true,允许客户端向服务端请求公钥,配合useSSL=false使用。 - 或者给连接加上 SSL 加密,满足安全传输要求。
不过,上面这两类配置在生产环境再怎么折腾,都没升级驱动来得省心。升级组件永远比绕开安全机制更值得推荐。
4.5 踩过的坑:那些看似"有效"但后患无穷的偏方
网上有一些解法,比如把用户的 plugin 直接改掉后,再把 MySQL 服务端的skip-grant-tables模式开起来强制重启。我只能说这种方法很危险——skip-grant-tables 意味着所有权限校验直接跳过,相当于数据库裸奔。如果你只是为了解决认证报错去开这个开关,等于拆了房子补窗户。千万不要在生产环境这么搞。
另一个坑,是有人在连接参数里把auth_plugin指定为mysql_native_password,但服务端用户实际还是新插件,这种配置在某些驱动下不一定生效,反而可能带来下一次的Access denied报错。优先通过数据库端确认用户的 plugin 状态,再决定从客户端还是服务端下手。
5. 个人实践经验与一点建议
这个报错在我手上出现过好多次,最早一次是在一个老 PHP 项目迁移到 MySQL 8.0 时踩到的,当时整个团队都很懵。后来慢慢地就有了一个固定的排查节奏:先查用户插件,再看客户端版本,最后根据场景决定改动方向。
目前我自己最推荐的组合是:服务端 MySQL 8.0 保持默认的 caching_sha2_password 不动,客户端统一升级到支持新协议的版本。这样既保障了密码安全,又能彻底告别这类兼容问题。Navicat 用 16.x、JDBC 用 mysql-connector-j 8.0.x、Python 用新版本 pymysql 或 mysql-connector-python、Node 用 mysql2,基本就一路畅通。
如果你实在无法升级客户端,才退而求其次去修改用户认证插件。改的时候记得只改具体用户,不要全局改配置,控制影响范围。
最后分享一个小技巧:如果你的 MySQL 8.0 已经上线跑了一段时间,团队里又存在多个开发环境,可以专门创建一个用于老客户端兼容的账号,统一设置成 mysql_native_password,其余账号保持默认。这样既照顾了开发工具的兼容性,又不影响整体安全策略。遇到类似问题复盘时,也能快速定位到是哪个账号、哪个环境的配置问题,排查起来轻松得多。