1. 这个报错到底在说什么
先还原一下场景:某天你在终端里连 MySQL,或者某个应用启动时突然抛出一行红色的错误:Plugin 'mysql_native_password' is not loaded。第一次见到的人很容易懵——这个mysql_native_password是什么?为什么没有加载?我密码没错啊?怎么突然连不上了?
这个报错通常出现在 MySQL 8.0 及以上版本的环境里,典型触发路径有两条:一条是你手动执行了CREATE USER ... IDENTIFIED WITH mysql_native_password BY '...',另一条是从 MySQL 5.7 把整个实例的default_authentication_plugin指到了mysql_native_password。结果服务端告诉你:我根本没有这个插件。听起来很像是环境坏了,但本质上不是环境问题,而是 MySQL 在认证机制上做了一次代际切换,你的使用习惯还停留在上一个时代。
MySQL 8.0 从发布起,默认的认证插件就是caching_sha2_password,不再是 5.7 时代人人都在用的mysql_native_password。mysql_native_password这个插件在 8.0 里虽然还带着“兼容模式”继续存在,但它默认不会主动加载。如果你没有显式开启,服务端在遇到IDENTIFIED WITH mysql_native_password这种写法时,就会直接甩出标题里这个错误。到 MySQL 8.4 和 9.0 时代,这个插件干脆连存在感都找不到了,直接进入禁用/移除序列。
这里要先说清楚一个关键认知:这个报错不是“MySQL 崩了”,也不是“密码错了”,更不是“权限问题”。它是 MySQL 的插件机制在告诉你——你指定的认证插件在当前实例里不存在,或者未被启用。想解决它,你有两条路可以走:一是把认证方式改回 MySQL 支持的默认插件;二是在 MySQL 里把mysql_native_password插件重新启用。但到底选哪条路,背后牵扯到客户端兼容性、安全策略、运维习惯等一系列问题,不是随便选一个就完事的。
这篇文章会把这个报错从原理到修复、从服务端到客户端、从快速解决到长期规划全部拆开讲一遍。我写的时候会尽量少讲废话,多给能直接落地的东西。不管你是刚把 MySQL 5.7 升到 8.0 的 DBA,还是正在被同事甩锅说“数据库连接挂了”的后端开发,读完应该都能自己判断选择哪种修复策略,并且顺手把客户端那一堆隐藏坑也填上。
2. 背后的插件机制与设计逻辑
2.1 mysql_native_password 的历史地位
mysql_native_password是 MySQL 从 4.1 时代开始用的认证插件,一直服役到 5.7,十几年没有怎么大改过。它的核心原理很简单:服务端保存一个密码的哈希值,客户端连接时用同样的哈希算法对密码做一次摘要,服务端比对摘要结果。整个过程不传明文密码,而且计算非常快,因此当年它是兼容性最好的选择,几乎所有语言的数据库驱动都天然支持它。
但是这套机制的安全短板也在业界被诟病了很久:它使用的 SHA-1 哈希算法已经被认为强度不够,而且它的挑战-响应机制在大量并发连接下存在中间人攻击的暴露面。简单说,它是“够用但不安全”的典型代表,只是大家用惯了,没有动力去换。
MySQL 官方在 5.7 时代就开始铺垫替代方案,把sha256_password插件塞了进来,但一直没转正为默认。真正下决心切换是在 8.0。MySQL 8.0 将默认认证插件改成了caching_sha2_password,它能做到和旧的mysql_native_password一样快的连接速度,但密码哈希强度更高、支持 RSA 公钥加密传输密码,并且更符合当前安全审计的要求。
2.2 为什么服务端不愿加载它
很多人在报错后第一反应是“那我在 my.cnf 里把默认认证插件改成 mysql_native_password 不就行了”。这个思路没错,但在 MySQL 8.0 里,你得先确认一件事:你的二进制版本里到底还带不带这个插件文件。
在 MySQL 8.0 的早期小版本(比如 8.0.0 到 8.0.18 左右),mysql_native_password作为内置插件随服务端一起提供,你说一声它就在。但在某些发行版、某些云厂商的托管数据库实例里,这个插件文件可能被有意移除了,或者被默认配置成了“不加载”。你在SHOW PLUGINS里看不到它,在INFORMATION_SCHEMA.PLUGINS里查不到它,自然一引用就报错。
到 MySQL 8.4 和更新的版本,情况更直接:MySQL 官方在 8.0 里宣布mysql_native_password是 deprecated(弃用),并在 8.4 版本中默认禁用这个插件,再到 9.0 直接移除。也就是说,如果你用的是 8.4 或 9.x,那么在服务端层面已经没有“启用这个插件”这个选项了,唯一的选择是让使用者改用caching_sha2_password。
MySQL 的设计逻辑其实是合理的:一个已经宣布弃用的认证插件,继续默认加载只会给所有人一种“我还能继续用”的错误安全感,还不如直接推一把,让大家迁移到新插件上。问题在于,生态里的大量老客户端、老框架、老连接池并不买账,这才出现了一批“明知新插件更好,但就是连不上”的项目。
2.3 报错触发的完整链条
这个报错的触发路径,我整理下来主要就三条:
- 你在 SQL 里显式指定了
IDENTIFIED WITH mysql_native_password BY,但目标服务端没有开启该插件。 - 你在
my.cnf或启动参数里设置了default_authentication_plugin=mysql_native_password,同样需要插件存在且可被加载。 - 某些第三方管理工具在生成建用户语句时,会自动带上
IDENTIFIED WITH mysql_native_password,你信任地拷贝执行,然后撞上这个报错。
无论哪条,最终 MySQL 的认证插件管理器都找不到对应的插件名,于是抛出Plugin 'xxx' is not loaded,并且顺带告诉你plugin is not loaded的同时不会继续执行之后的账号创建逻辑。所以你会看到哪怕只是建用户这一个动作,后面的授权语句压根不会走到。这里我建议所有人在排查时先分清“语法报错”和“插件报错”,因为这类报错和Unknown authentication plugin很容易搞混。
3. 修复方案对比与选型分析
3.1 方案A:重建用户并改用 caching_sha2_password
这个方案的核心思路是:不与服务端对着干,承认mysql_native_password已过时,把所有新账号全部改用caching_sha2_password。
具体操作很简单。先用一个有CREATE USER权限的管理员账号登入,然后执行:
CREATE USER 'app_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'StrongPassword123!'; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'%'; FLUSH PRIVILEGES;如果是已有的用户,想从mysql_native_password或者报错状态里转过来,可以这样改:
ALTER USER 'app_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'StrongPassword123!';执行完再用客户端工具测试连接。只要能连上,说明服务端这边已经搞定了。
这个方案最大的优势是符合 MySQL 8.0 以后的默认策略,以后升级到 8.4、9.0 都不会再遇到插件兼容问题。但它的前提是你的客户端、JDBC 驱动、ORM 框架、连接池组件都支持caching_sha2_password。目前主流的 MySQL 官方驱动、开源的 ORM 框架基本都兼容了,唯一需要特别注意的是一些老旧的、不再维护的内部组件,以及部分低版本开发语言驱动。如果你的应用恰好踩中这些雷,那就需要在客户端也同步做调整。
3.2 方案B:服务端重新启用 mysql_native_password
这个方案适合短暂过渡。
首先确认下你的 MySQL 版本是否还支持这个插件。如果是 8.0 的某个版本,可以通过配置文件开启。在my.cnf或my.ini的[mysqld]段加上:
[mysqld] mysql_native_password=ON或者:
[mysqld] default_authentication_plugin=mysql_native_password修改之后重启 MySQL 服务。重启完登录 MySQL,执行:
SHOW PLUGINS;如果看到一行:
mysql_native_password | ACTIVE | AUTHENTICATION | NULL说明插件已经加载成功。这时候你再执行CREATE USER IDENTIFIED WITH mysql_native_password就不会报错了。
但这个方案有两点必须提醒你:
第一,mysql_native_password=ON这个配置项在 MySQL 8.0 的早期和后期版本写法有差异,遇到不生效时需要用default_authentication_plugin兜底。第二,如果你使用的是云数据库托管服务,很可能根本没权限修改服务端全局配置。那一类环境下,方案B直接走不通,只能走方案A,或者在云平台的控制台参数组里看看有没有同名参数可以改。
3.3 两个方案的隐蔽成本
从技术动作上看,方案B比方案A简单得多,一条配置重启就完事。但从项目长期健康度来看,方案B是典型的“短期止痛,长期留债”。
我见过不少团队为了让某个三五年没更新过的内部系统连上 MySQL 8.0,直接全局改回mysql_native_password。表面上所有系统都恢复了,但隐患在于:
- 全局使用
mysql_native_password意味着新创建的账号也全是旧认证方式,以后想迁回新插件又要逐个清理。 - 安全扫描或等保检查大概率会把这个插件列为高风险项,审计的时候很难解释。
- 当 MySQL 版本最终升到 8.4 或 9.x,这个配置项直接失效,系统会再次面临同样的报错,且到时连临时回退方案都没得选。
这不是说方案B绝对不能碰,而是说你要把它当一个有时间窗口的短期过渡手段,而不是长期配置。我个人建议:如果只是临时给某个老服务开一条逃生通道,用方案B;如果是在做一个新项目,或计划长期维护一个系统,直接用方案A,一步到位不折腾。
4. 实战修复过程与验证清单
4.1 先确认当前服务端的插件状态
不管最终选哪个方案,动手修复之前,第一件事永远是搞清楚 MySQL 实例现在知道哪些插件。这一步不会改任何东西,纯查状态,用来定位问题边界。
登录 MySQL 后执行:
SHOW PLUGINS;输出的表格里会列出所有插件及状态。重点看最后几行关于sha2_password、caching_sha2_password、mysql_native_password的记录。如果是 8.0 但未见mysql_native_password,那就确认了——当前实例没有加载这个插件。
也可以用更精确的查询:
SELECT plugin_name, plugin_status FROM information_schema.plugins WHERE plugin_name LIKE '%password%' OR plugin_name LIKE '%sha2%';这个查询可以让你在SHOW PLUGINS输出太长的情况下快速过滤出认证相关插件。
还要顺便看下默认认证插件设置:
SHOW VARIABLES LIKE 'default_authentication_plugin';在 MySQL 8.0 里,返回值应该是caching_sha2_password。如果这个值已经被改成mysql_native_password,而插件又没有加载,那就会出现更魔幻的报错:不光建用户失败,连普通密码登录可能都会异常。
4.2 用户级修复的执行细节
确认插件状态后,走方案A的可以直接操作。
这里给一个更完整的示例。假设应用需要连接order_db库,账号名叫order_app,允许从内网网段192.168.10.%连接:
CREATE USER 'order_app'@'192.168.10.%' IDENTIFIED WITH caching_sha2_password BY 'Order@2024#Secure'; GRANT SELECT, INSERT, UPDATE, DELETE ON order_db.* TO 'order_app'@'192.168.10.%'; FLUSH PRIVILEGES;注意:密码里如果包含特殊字符,建议用强密码但避开容易在连接串里引起转义问题的符号。比如@、#、%在 URL 连接串里如果没转义,会直接被解析成特殊用途字符,这是最常见的“SQL能建但代码连不上”事故源头。
再强调一个细节:FLUSH PRIVILEGES在创建和授权语句之后不是必须的,但多人操作同一套环境的场景下,执行一下能避免一些肉眼看不见的权限缓存问题。它的开销不大,就当买个保险。
如果是ALTER USER方式修复已有账号,尤其要注意不能写漏BY子句。只写ALTER USER 'x'@'%' IDENTIFIED WITH caching_sha2_password;不会报语法错误,但会把密码改没了。轻则连接被拒,重则整个应用不可用。
4.3 全局配置修改的落地操作
如果确实要走方案B,建议先把配置文件备份一份再改。
以 Linux 上的 MySQL 8.0 为例:
cp /etc/my.cnf /etc/my.cnf.bak.$(date +%Y%m%d%H%M%S)随后编辑my.cnf:
[mysqld] default_authentication_plugin=mysql_native_password保存后重启服务:
systemctl restart mysqld重启后再次进入 MySQL 执行前面的插件状态查询。如果插件状态显示 ACTIVE,那就试建一个用户:
CREATE USER 'legacy_user'@'%' IDENTIFIED WITH mysql_native_password BY 'LegacyPass!123';没有报错就说明全局启用成功了。
如果用的不是 systemd 而是旧式 init 脚本,用service mysql restart也行。有些版本重启后可能报错unknown variable 'default_authentication_plugin=mysql_native_password',这通常意味着该版本已不支持此参数,或者插件文件已被编译时去除。此时继续挣扎没有意义,直接返回方案A。
4.4 完整验证清单
很多人在修复完服务端就立刻去连数据库,连上了就说“搞定了”。但真正的完整验证应该包含以下几个维度:
- 服务端状态:
SHOW PLUGINS能看到目标插件状态为ACTIVE。 - 用户信息:执行
SELECT user, host, plugin FROM mysql.user WHERE user='你的账号';,确认账号的 plugin 字段与预期一致。 - 明文密码登录:用 MySQL 命令行客户端,用账号密码真实登录一次,不能跳过。
- 应用层连接:先用本地脚本或 API 工具连接一次,再用业务代码连接一次,两层都要通。
- 字符集和时区:连接成功后查看
SHOW VARIABLES LIKE 'character_set%'和SELECT NOW();,防止修复认证插件后暴露出新的配置问题。
我自己在实战中遇到最多的情况是:命令行列客户端连上了,但应用连不上。因为应用侧的连接参数里往往还带着老驱动或旧配置。所以验证清单里,应用层连接是绝对不能跳的一步。
5. 客户端适配与连接串调参
5.1 各类驱动对 caching_sha2_password 的支持差异
服务端切到caching_sha2_password之后,客户端的支持情况会成为新的瓶颈。拿最常用的几种来说:
- MySQL 官方 JDBC 驱动(Connector/J):8.0.9 及以上版本完全支持
caching_sha2_password,无需额外操作。但连接串里强烈建议加allowPublicKeyRetrieval=true,否则在某些非 SSL 连接场景下驱动无法自动获取服务端公钥,会报Public Key Retrieval is not allowed。 - PHP 的 mysqli 和 PDO_MySQL:PHP 7.4.20 之后的版本基本支持,但老版本 PHP 7.1、7.2 在连接时会报
Server sent public key相关错误,应对方式是升级 PHP 或在服务端配置mysql_native_password过渡。 - Python 的 PyMySQL:需要安装较新版本或改用
mysql-connector-python。使用 PyMySQL 时,如果服务端认证插件是caching_sha2_password,需要在创建连接时显式指定auth_plugin='caching_sha2_password'。 - Go 的 go-sql-driver:较新版本兼容,但部分旧版本驱动需要加
allowCleartextPasswords=true之类的参数。
这个阶段最容易踩的坑是把责任全推给服务端,拼命查 SQL 语句。实际上服务端已经改了,剩下就是客户端驱动版本的问题。排查建议是先看官方发布日志,确认版本对caching_sha2_password的支持状态。
5.2 一个典型的 JDBC 连接串改造
以 Java 后端最常见的 Spring Boot 项目为例,MySQL 8.0 下比较稳妥的 JDBC URL 长这样:
jdbc:mysql://192.168.10.20:3306/order_db?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true对比老式连接串,新增或改动的关键参数是allowPublicKeyRetrieval=true。在 MySQL 8.0 默认启用caching_sha2_password后,如果连接不是 SSL,驱动需要额外获取 RSA 公钥来完成密码传输。这个参数默认是 false,会直接导致连接失败。加上它能解决非 SSL 场景的兼容问题。
另一个经常被忽略的参数是useSSL=false和serverTimezone。前者是显式关掉 SSL,避免 SSL 相关的手续;后者是为了防止时区报错The server time zone value 'CST' is unrecognized。虽然这两个问题和认证插件没有直接关系,但在服务端刚完成版本升级后最容易接连出现,我先写在这里供参考。
如果你所在的企业要求必须走 SSL,那么useSSL=true时还需要配置证书相关参数,这又是另一套折腾。我见过不少小团队为了省事,最终选择在非 SSL 模式下加allowPublicKeyRetrieval=true,对内部系统来说安全性尚可,关键是先把连接跑通。
5.3 Python 场景的写法参考
Python 项目里用 PyMySQL 时,遇到mysql_native_password is not loaded之后如果服务端已经切到caching_sha2_password,连接代码建议这样写:
import pymysql conn = pymysql.connect( host='192.168.10.20', port=3306, user='order_app', password='Order@2024#Secure', database='order_db', charset='utf8mb4', auth_plugin='caching_sha2_password' )auth_plugin参数不是所有版本的 PyMySQL 都兼容,老版本会直接忽略。如果传了还报错,优先升级包版本。用mysql-connector-python的话,连接参数类似,但通常不需要显式指定auth_plugin,它会自动从服务端协商。
5.4 连接池里那些“诡异”的间歇性失败
服务端、驱动都改好了,应用也能连上了。但上线后你可能偶尔收到连接池报错“Access denied”或“Communications link failure”。这种情况大概率不是代码问题,而是连接池里还缓存着旧密码或旧认证方式。
因为连接池在应用启动前就预建好了若干连接,你用 SQL 改了用户密码或认证插件,连接池并不会自动感知,仍然拿着旧凭据做保活校验。这类问题的排查手段是:
- 重启应用,清空连接池缓存,看问题是否消失。
- 或直接执行
ALTER USER之后,在测试环境不等连接池回收,手动KILL曾经建立的空闲连接。
有些连接池框架提供了重新校验连接的开关,比如 DBCP 的testWhileIdle,默认不一定开启。建议在正式环境里打开连接有效性检测,避免升级过程中线上应用突然被连接断开砸挂。
6. 踩坑实录与排查速查表
6.1 我踩过的几个坑
第一个坑:在 MySQL 8.0.34 版本上直接改my.cnf写mysql_native_password=ON,重启后配置被 MySQL 静默忽略,没有报错也没有生效。后来查官方手册才发现,这个参数在较新的 8.0 里已经被换成default_authentication_plugin作为唯一的控制开关,旧写法虽然不报错但直接失效。如果你沿用网上的老教程,很容易被这种“不报错但不生效”的情况坑到。
第二个坑:云数据库实例上执行SHOW PLUGINS看不到mysql_native_password,但同样也没权限改default_authentication_plugin。这种情况下,方案B完全不可行,只能在账号级别使用caching_sha2_password,然后把所有老客户端升级到兼容版本。这个坑尤其容易出现在从自建机房迁移上云的场景里。
第三个坑:账号里设置了IDENTIFIED WITH mysql_native_password,但密码本身包含#字符。在 JDBC 连接串里没有做 URL 编码,导致驱动解析密码串时截断成空密码,报错信息完全指向“Access denied”。排查了半小时服务端,结果只是连接串编码问题。血的教训:强密码生成后,最好先在连接串里用浏览器工具做一次 URL 编码再粘贴进去。
第四个坑:PHP 老版本 + MySQL 8.0 组合。服务端密码没错、插件没错、权限没错,但 PHP 7.2 的 mysqli 就是不支持caching_sha2_password。这里一度让我以为是 PHP 扩展没装好,后来把报错信息Server sent public key丢给搜索引擎,才意识到是驱动版本太老。所以排查时不要只看第一条报错信息,要把完整堆栈拉出来看。
6.2 排查速查表
我把整个排查过程整理成一张表,遇到报错可以直接对着操作:
| 报错特征 | 可能原因 | 快速处置方法 |
|---|---|---|
Plugin 'mysql_native_password' is not loaded | 服务端未启用或移除了旧认证插件 | 查看SHOW PLUGINS,改用caching_sha2_password创建用户 |
Public Key Retrieval is not allowed | 客户端驱动缺少allowPublicKeyRetrieval参数 | JDBC 连接串加allowPublicKeyRetrieval=true |
Access denied for user | 密码错 / 账号 host 不匹配 / 连接串密码被特殊字符截断 | 命令行先试登录,再逐项检查连接串 |
Server sent public key | 驱动版本过老,不支持新认证插件 | 升级驱动,或临时回退到旧认证插件 |
Communications link failure | 连接池缓存 / 防火墙 / SSL 握手失败 | 重启应用验证,检查防火墙端口和 SSL 参数 |
Unknown authentication plugin 'caching_sha2_password' | 客户端驱动完全不认识新插件 | 必须升级驱动,没有其他捷径 |
这张表基于我处理过的多数同类故障,能覆盖七成以上的场景。剩下的三成,多半是因为业务系统本身有定制化组件,没法用通用方案解决,那就要回头从“你用的中间件到底支持什么插件”这个原点去查。
6.3 关于使用 mysql_native_password 的合规提醒
最后多写一段。很多团队在修复完报错后,会让服务端长期停留在mysql_native_password方案上。从纯技术角度来说,短时间内确实没问题,但你要知道,MySQL 官方已经把该插件标记为废弃。某安全审计、等保评测时,扫描器扫出这个插件是高风险项,你会需要提供额外的补偿说明。与其到时候解释一堆,不如在第一次碰到报错时就把规划往caching_sha2_password方向靠。
如果你的系统里还有一堆不可控的老客户端,无法短期内全部升级,那我的建议是:以账号维度做分层管理,新系统一律用caching_sha2_password,老系统设定一个“过渡窗口期”临时用mysql_native_password,并排期尽快改造,而非全局统一回退。这个思路能在“尽快恢复服务”和“长期健康”之间取得一个平衡。
7. 从一次报错看到的长远思路
处理完这一次Plugin 'mysql_native_password' is not loaded的报错,你对 MySQL 认证机制的理解大概率会上一个新台阶。这个报错真正想教你的不是某个命令,而是:数据库升级从来不是数据库自己升级,而是数据库加上它周边的一切客户端、驱动、工具链一起升级。
我个人的习惯是,每次做 MySQL 大版本升级前,先列一张“客户端兼容性矩阵”,把项目里用到的所有数据库驱动、ORM、连接池版本列出来,逐个确认对目标版本新特性的支持情况。这件事听起来麻烦,但比升级到一半被各种稀奇古怪的报错打断要省时得多。
再分享一个小技巧:如果你在测试环境里模拟这个报错,不用专门去卸插件。创建一个用户时显式指定一个不存在的插件名(比如CREATE USER 'test'@'%' IDENTIFIED WITH some_fake_plugin BY '123456';),就能复现同款Plugin 'xxx' is not loaded报错。用这个方式反复练习,你会对 MySQL 的插件加载机制有更直观的感觉。
数据库的认证插件这件事,本质上就是在安全性和兼容性之间做选择。MySQL 已经用行动告诉了你它选哪边,剩下的就是你的项目跟上步伐而已。