上周五有个朋友发来一张 MySQL 报错截图ERROR 1251 (08004): Client does not support authentication protocol requested by server; consider upgrading MySQL client。他说密码确认了好几遍没问题3306 端口也是通的但不管是 Navicat 还是命令行都连不上。这个问题在 MySQL 8.0 普及之后几乎每天都能在技术群里看到本质上是客户端和服务端在“用哪把钥匙开门”这件事上没谈拢跟密码对不对还真没多大关系。这篇文章我就基于这个报错把它的来龙去脉、几种靠谱的修复方案、我在真实服务器上踩过的坑以及 Docker、远程连接、应用驱动这些高频场景一并梳理清楚。MySQL 8.0 之后装完库老是冒出奇怪连接问题的朋友建议直接把这篇存下来当排查手册用。1. 1251 报错的本质客户端与服务端的认证握手失败1.1 MySQL 8.0 换了认证插件老客户端直接懵了要搞明白 1251 报错先得知道 MySQL 的认证机制是怎么工作的。MySQL 服务端在收到客户端连接请求后会先检查用户名和 host 匹配的用户记录然后把该用户使用的认证插件authentication plugin信息发给客户端双方按这个插件约定的算法完成密码验证。这一步就是“认证握手”。MySQL 5.7 及更早的版本默认认证插件是mysql_native_password密码哈希基于 SHA1虽然简单但兼容性极广几乎所有的老客户端、老驱动都能认识。到了 MySQL 8.0官方把默认认证插件换成了caching_sha2_password算法更安全密码存储和处理机制都变了。问题在于客户端也得认识这个新插件才行如果客户端程序版本太老只见过mysql_native_password当服务端告诉它“我要用 caching_sha2_password 验证你”时客户端直接回一句“这是什么玩意我没法支持”于是服务端抛出 1251。这就好比你把家里的门锁从普通弹子锁换成了智能指纹锁以前那把老钥匙就算齿形完全正确也插不进锁孔。1251 报错就是这种“锁孔都进不去”的状态密码对不对根本不重要因为验证流程压根没走到那一步。需要特别提醒的是这个报错和 1045 报错有本质区别。1045 是Access denied for user xxxxxx (using password: YES)意思是认证流程正常走完了但密码或者账号权限不对。如果你看到的是 1251就别再反复试密码了先检查客户端版本和服务端认证插件配置才是正路。1.2 哪些客户端最容易中招从实际遇到的案例来看1251 报错主要集中在以下几类客户端上Navicat 11 及更早版本或者 12 的某些早期小版本老旧的 JDBC 驱动比如 mysql-connector-java 5.1.xPHP 7.2 及以下版本中自带的 mysqlnd 驱动Python 的 MySQLdbmysql-python以及 PyMySQL 0.9.3 之前的版本MySQL Workbench 6.x 和 8.0 的早期版本系统自带的 mysql 命令行客户端比如旧版 CentOS/Ubuntu 上通过 apt/yum 装的 mysql 5.x 客户端部分老版本 DBeaver、SQLyog、HeidiSQL 等图形化工具如果你的使用场景是“新部署的 MySQL 8.0/8.4 服务器 公司里统一安装的老客户端”那一撞一个准。另外MySQL 8.4 开始情况又有变化后面我会单独讲。2. 最快解法把用户认证插件改回 mysql_native_password2.1 适用场景和动手前的确认最直接有效的临时解法就是把目标用户比如 root的认证插件从caching_sha2_password改回mysql_native_password。这个方法特别适合以下场景客户端程序无法升级比如生产环境里跑着一个用了八年的 Java 老项目驱动是 5.1.x不好动公司电脑上统一安装了老版本 Navicat没有权限换新版本临时救急先让业务恢复再慢慢规划客户端升级不过在动手之前有几件事必须先确认好否则容易白忙活确认当前 MySQL 服务端版本。执行SELECT VERSION();即可。确认要修改的用户在mysql.user表中对应的 host 记录。这是最容易踩坑的地方我后面会专门说。确认自己能用一个有权限执行ALTER USER的账号登录比如 root。你可以在本机用 mysql 命令行登录后先执行下面这条语句把当前用户的插件类型看清楚SELECT user, host, plugin FROM mysql.user WHERE user root;如果查询结果里 plugin 那一列是caching_sha2_password那基本就实锤了。2.2 执行 ALTER USER 的完整步骤确认之后执行一条ALTER USER语句即可。语法如下ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;这里有几个细节需要展开解释第一IDENTIFIED WITH mysql_native_password的意思是指定认证插件。这句话理解成“以后这个用户就用 mysql_native_password 这种老式验证方式密码临时设为后面的字符串”。如果你不希望改变密码那就把后面的你的密码填成原来的密码即可相当于只换插件不动密码。第二host 部分必须和mysql.user表里的记录完全一致。比如查出来有两行rootlocalhost和root%那就必须分别对两条记录执行ALTER USER只改其中一个是没用的。我自己就犯过这个错远程客户端一直报 1251结果发现只改了localhost而远程连接匹配的是%那条记录。第三执行完ALTER USER之后不需要执行FLUSH PRIVILEGES。因为ALTER USER这种语法操作权限表是即时生效的MySQL 会自动重新加载权限。只有当你直接UPDATE mysql.user表里的 plugin 字段时才需要手动FLUSH PRIVILEGES。虽然加上也不报错但没必要。如果你坚持要用 UPDATE 直接改表语句可以这样写UPDATE mysql.user SET plugin mysql_native_password WHERE user root AND host %; FLUSH PRIVILEGES;但我还是推荐ALTER USER因为直接改表属于“抄近路”容易漏掉其他地方的状态同步而且一旦拼错字段名影响面不好控制。修改完成后用客户端重新连接测试。如果还不行先确认你是否把密码中的特殊字符处理好了。比如密码里有、#、$这些符号命令行里直接拼在-p后面很容易被 shell 吃掉或当成变量建议先在命令行用交互方式试mysql -uroot -p然后回车再输入密码这样最稳妥。3. 更靠谱的长期解法升级客户端 正确配置连接参数3.1 不同客户端到底升到哪个版本才支持改服务端认证插件虽然快但本质上是“让安全等级高的服务端去迁就老客户端”。如果这是一个长期项目我强烈建议反过来把客户端和驱动升级到支持caching_sha2_password的版本这样既保留 MySQL 8.0 默认的安全策略又不会埋下兼容性地雷。各主流客户端的最低支持版本我根据实际测试整理在下面这张表里客户端 / 驱动支持 caching_sha2_password 的最低版本备注Navicat12.0.29 以上建议 15 / 16早期版本即使能连也可能不稳定mysql-connector-java (JDBC)8.0.x5.1.x 全部不行PyMySQL0.9.3建议 1.0还需要安装 cryptography 库mysqlclient1.4.6用了 Python 3 的话直接上最新版PHP mysqlndPHP 7.4PHP 7.2 / 7.3 建议用 mysql_native_passwordMySQL Workbench8.0.11对应 MySQL 8.0.11DBeaver21.x 之后基本无压力社区版即可升级完之后还要注意一点如果你的业务代码里写死了连接串只升级驱动是不行的连接参数也要跟着调整。3.2 不改服务端插件连接参数怎么配当你确定升级了客户端但连接时仍然遇到认证相关报错尤其是出现 “Public Key Retrieval is not allowed” 这样的提示问题就出在连接参数上。caching_sha2_password在没有启用 SSL 的时候密码是通过 RSA 公钥加密传输的。客户端需要先向服务端请求公钥而 MySQL 默认不允许客户端自动获取公钥必须显式开启allowPublicKeyRetrievaltrue。这就是为什么很多人明明驱动版本没问题还是一直报认证失败的直接原因。Java JDBC 的连接串示例jdbc:mysql://192.168.1.100:3306/app_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai这里useSSLfalse是为了省去 SSL 证书配置allowPublicKeyRetrievaltrue才是关键。还要注意serverTimezone参数MySQL 8.0 的 JDBC 驱动对时区敏感不配置容易报错。Python 的方案更简单安装 PyMySQL 后确保也安装了 cryptographypip install PyMySQL cryptography然后在代码里正常连接即可import pymysql conn pymysql.connect( host192.168.1.100, userapp_user, passwordxxxx, databaseapp_db, charsetutf8mb4 )如果你用的是 Navicat 这类图形化工具升级到最新版后一般不需要额外设置。但连接时如果弹出关于 RSA 公钥的提示记得在连接属性的高级选项里把对应开关打开不同版本 UI 不一样找不到就直接升级客户端。还有一个全局层面的方案如果你希望整个实例新建用户时都默认使用老插件可以在 my.cnf 的[mysqld]段落加一行[mysqld] default_authentication_pluginmysql_native_password然后重启服务。不过这个方案我不推荐在生产环境随便用因为它是把整个实例的默认安全等级拉低了影响面太大。更合理的做法是哪几个业务用户需要老插件就单独给哪几个用户设置。4. 实操复盘从报错到恢复的全过程4.1 故障现场与排查思路上个月我接手了一个 Java Web 项目的部署服务器是 CentOS 7.9MySQL 版本 8.0.36用 tgz 方式安装在/usr/local/mysql。项目配置里写的是 JDBC 5.1.40 驱动数据库账号是rootlocalhost密码是运维当初随手设的Pssw0rd!。现象非常典型项目日志里抛Unable to load authentication plugin caching_sha2_passwordNavicat 15 连接也一直弹 1251 窗口。当时我的排查顺序是第一步先确认 MySQL 服务本身是不是正常的。用服务器的 mysql 命令行登录mysql -uroot -p能正常登录说明数据库进程没问题也说明服务器上这个 mysql 命令行版本跟服务端是配套的不存在协议问题。第二步确认网络和端口。远程 telnet 一下telnet 192.168.1.100 3306通了。这一步很重要因为如果业务连不上首先要排除端口没监听、防火墙拦截、bind-address 限制这些底层问题。1251 报错是在端口通、服务正常的前提下才会出现的如果你看到的报错是 2002那就是另一个故事了。第三步登录 MySQL 查用户表SELECT user, host, plugin FROM mysql.user WHERE user root;结果userhostpluginrootlocalhostcaching_sha2_password问题确认了root 用户用的是新认证插件而项目里的老驱动只认识mysql_native_password。4.2 踩过的坑与修复动作我当时的第一反应是直接把 root 改成老插件。执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY Pssw0rd!;这个语句本身没问题执行后 Navicat 也能连上了。但坑就在后头项目部署在生产环境时用的是另一个账号webapp%我查表时没看webapp这个用户只看了 root结果前端应用还是持续报 1251。后来我认真查了一遍所有用户SELECT user, host, plugin FROM mysql.user;发现webapp%也是caching_sha2_password补了一句ALTER USER webapp% IDENTIFIED WITH mysql_native_password BY Webapp123;这才全线恢复。这个过程中还踩了一个小坑密码Pssw0rd!里带和!在 bash 命令行里直接执行mysql -uroot -pPssw0rd!时单引号包裹没问题但如果在脚本里不加引号就会被 shell 错误解析成变量引用导致一直提示密码错误。所以涉及特殊字符密码我的建议是尽量用交互式登录或者写在配置文件里不要直接拼在命令行参数中。另外还有一个值得注意的点如果你要改的用户是老项目在用的 root建议改完之后顺手验证一下其他客户端是否还能正常连接。因为有些旧客户端可能对mysql_native_password也没有完整支持比如极老版本的 PHP mysql 扩展。不过这属于极端情况正常业务里遇不到。4.3 验证与遗留事项修复完成后我做了三个验证动作用 Navicat 重新连接确认能进入数据库。用项目里的 JDBC 连接串重新跑了一个测试用例确认业务层正常。再次查询插件字段确认修改已生效SELECT user, host, plugin FROM mysql.user WHERE user webapp;由于这次是老驱动 新库的组合我没有动标准配置所以给webapp单独设置老插件是最小改动方案。但我也在企业文档里记了一条建议下次迭代时一定要把 JDBC 驱动从 5.1.40 升级到 8.0.33同时把连接串加上allowPublicKeyRetrievaltrue和useSSLfalse到时候就可以把webapp恢复成默认的caching_sha2_password安全等级也能回满。5. 多场景扩展Docker、远程库和应用侧防坑5.1 Docker 容器内 MySQL 的认证修复现在很多团队直接用 Docker 跑 MySQL比如docker run -d --name mysql8 \ -e MYSQL_ROOT_PASSWORDRoot123 \ -p 3306:3306 \ mysql:8.0容器起来后如果宿主机的 Navicat 报 1251处理方法一样只是要先进入容器执行 mysql 命令docker exec -it mysql8 mysql -uroot -p然后执行 ALTER USERALTER USER root% IDENTIFIED WITH mysql_native_password BY Root123;这里有个小细节Docker 官方镜像创建的 root 用户通常会自动生成root%和rootlocalhost两个条目你需要根据 Navicat 连接时的实际 host 来决定改哪一个。远程连接一般配的是%。如果你希望部署容器时就一步到位可以在挂载/docker-entrypoint-initdb.d/目录的初始化脚本里直接创建专用用户并指定认证插件。比如新建一个init.sqlCREATE USER app% IDENTIFIED WITH mysql_native_password BY App123456; GRANT ALL PRIVILEGES ON app_db.* TO app%; FLUSH PRIVILEGES;启动容器时挂载进去docker run -d --name mysql8 \ -e MYSQL_ROOT_PASSWORDRoot123 \ -v /opt/mysql-init:/docker-entrypoint-initdb.d \ -p 3306:3306 \ mysql:8.0这样初始化完成之后app用户直接就是老插件应用侧连接不会碰 1251。要注意的是/docker-entrypoint-initdb.d下的脚本只在数据卷首次初始化时执行一次容器重启不会重新执行。如果你已经跑过容器再挂脚本是不会生效的。另外容器重启后用户认证插件不会自动变回去因为认证插件的修改是持久化在系统表里的除非你把数据目录整个删掉重建或者用旧数据卷的快照覆盖否则不用担心“重启失效”的问题。5.2 远程库表同步场景热词里有人提到“把远程库的这张表同步到本地”其实这个需求在 MySQL 认证问题解决后反而是个小事。比如你想把远程服务器的某张订单表同步到本地最常见的是用mysqldump做单表导出导入。先在本机用支持新认证的客户端去连远程库导出单表mysqldump -h192.168.1.100 -uwebapp -pWebapp123 --single-transaction app_db orders orders.sql如果本地 mysql 命令行版本太老mysqldump同样会因为不支持caching_sha2_password报 1251这时候要么升级本地客户端要么让远程库给你单独开一个使用mysql_native_password的只读账号CREATE USER sync你的IP IDENTIFIED WITH mysql_native_password BY Sync2024; GRANT SELECT ON app_db.orders TO sync你的IP;这种账号只授单表 SELECT 权限业务安全性也更有保障。然后导入本地mysql -uroot -p local_db orders.sql其实很多“连不上”的问题根源都在认证协议而不是同步本身先把认证链路打通后面的事都顺了。5.3 企业里更安全的实践别把 root 改来改去修复 1251 时我见过不少同事图省事直接ALTER USER root% IDENTIFIED WITH mysql_native_password BY xxxx;一条语句搞定所有问题。短期看确实爽但长期看隐患很大整个团队所有应用和同事都在用 root 连数据库一旦 root 的认证方式被降低等于把整个实例的安全水位都拉低了而且后续排查问题也很难分清是哪个业务在连。更推荐的做法是给具体业务单独建一个专用账号按需授权。比如某个 Python 服务需要访问app_db就执行CREATE USER python_app% IDENTIFIED WITH mysql_native_password BY PythonApp2024; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO python_app%;这样业务侧即使必须用老插件影响范围也只在这个账号内root 仍然保持caching_sha2_password。哪天业务升级、驱动支持新认证了只需再改这一个账号的插件不用动整个实例。5.4 顺带提一个 8.4 的坑如果你用的是 MySQL 8.4 或更高版本情况又不太一样。从 8.4 开始官方默认禁用mysql_native_password插件如果你直接执行ALTER USER ... IDENTIFIED WITH mysql_native_password BY ...可能会提示插件被禁用或者直接报错。解决办法是在配置文件的[mysqld]段落里显式开启[mysqld] mysql_native_passwordON然后重启 MySQL 服务再执行 ALTER USER。这个操作影响的是整个实例也是把所有用户的老插件开关都打开了所以还是那句话非必要不全局开能建专用账号就建专用账号。如果你项目已经用上 8.4最好的策略反而是升级所有客户端去适配默认的caching_sha2_password而不是反过来迁就老环境。6. 高频问题速查表和日常防坑建议6.1 报错对照速查表我在工位上处理连接问题的时候经常用下面这张表做快速定位也分享给遇到类似问题的朋友报错信息常见原因快速处理方法ERROR 1251 (08004): Client does not support authentication protocol requested by server客户端太老不支持 caching_sha2_password升级客户端或 ALTER USER 改用 mysql_native_passwordERROR 2059: Authentication plugin caching_sha2_password cannot be loaded驱动版本太老加载不了新插件升级 JDBC / Python / PHP 驱动到支持新插件的版本ERROR 1045: Access denied for user密码错误或账号无权限重置密码或检查 GRANT 授权ERROR 2002: Cant connect to local MySQL server through socketMySQL 服务未启动或 socket 路径不对检查服务状态和 my.cnf 里的 socket 配置ERROR 1698: Access denied for user rootlocalhostUbuntu 等系统安装后默认用 auth_socket 插件改用 sudo mysql 进入再 ALTER USER 换认证方式java.sql.SQLException: Public Key Retrieval is not allowed使用 caching_sha2_password 且未开启公钥获取连接串加 allowPublicKeyRetrievaltrue这张表里第一行和第二行是最容易混淆的。简单区分一下1251 是“客户端直接表示不支持这个协议”2059 是“客户端尝试加载插件但加载不了”本质上都是新老认证机制不匹配解决方向一致都在客户端升级和服务端插件调整二选一。6.2 日常部署时三条防坑建议最后说几条我自己用了很久的实践能帮你省掉很多“事后救火”的时间。第一新装 MySQL 8.0 之后第一步不是建库建表而是把所有可能的连接方列出来确认客户端和驱动的版本。命令行客户端、JDBC、Python、PHP、Navicat逐个核对把版本号写进项目部署文档。这一步花十分钟能避免上线当天集体 1251。第二业务账号永远比 root 好用。新建业务库时顺手创建专用账号并指定认证插件而不是全部用 root长期看会省很多心。比如初始化语句写成CREATE USER app_user% IDENTIFIED WITH mysql_native_password BY StrongPass123;以后客户端升级了只改这一条。第三连接串里的参数要写全。Java 项目至少把useSSL、allowPublicKeyRetrieval、serverTimezone三个参数补齐Python 项目记得装 cryptographyPHP 项目注意 mysqlnd 版本。很多时候报错不是数据库的问题而是连接参数缺项导致的“二次故障”。处理 1251 这个报错我最大的体会是先查mysql.user表里的 plugin 字段再决定改客户端还是改服务端顺序千万别反。如果你一上来就到处找“什么是 1251”大概率会在密码和端口上白白浪费半小时。记住那个“钥匙和锁”的比喻这个问题本质上就是老钥匙开不了新锁要么换钥匙升级客户端要么换锁芯改认证插件没有第三个魔法开关。希望这篇内容能让你少走几步弯路。
