输对密码仍报Access denied,根本原因在于MySQL认证依赖user@host+plugin三元组匹配:caching_sha2_password插件不兼容旧客户端、localhost与127.0.0.1被视作不同主机、空用户''@'localhost'优先匹配干扰,需查mysql.user表并针对性执行ALTER USER与FLUSH PRIVILEGES。

为什么输对密码还是报 Access denied
根本不是密码错了,而是 MySQL 认证流程在插件、host 匹配或空用户上卡住了。常见现象包括:ERROR 1045 (28000): Access denied for user 'root'@'localhost',哪怕你刚用 ALTER USER 设了新密码。
- 认证插件不兼容:MySQL 8.0+ 默认用
caching_sha2_password,但老版客户端(如 Navicat 15 以下、PHP 7.4 的 mysqlnd、某些 Docker 镜像自带的 mysql 命令)不支持它 - host 匹配错位:
mysql -u root -p走 Unix socket,匹配的是'root'@'localhost';而mysql -h 127.0.0.1 -u root -p走 TCP,匹配的是'root'@'127.0.0.1'——这两个账号密码、插件可以完全不同 - 空用户干扰:存在
''@'localhost'这种匿名账号时,它会优先于'root'@'localhost'被选中,导致你输 root 密码却进了无权限会话
检查并切换认证插件
先确认当前 root 用户用的是哪个插件:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';
如果 plugin 是 caching_sha2_password,且你没法升级客户端,就切回兼容性更强的 mysql_native_password:
- 执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的强密码'; - 别漏掉:
FLUSH PRIVILEGES;(虽然 8.0 多数情况自动生效,但改过系统表或不确定时,执行更稳妥) - 如果要同时支持本地 socket 和 TCP 连接,补上:
ALTER USER 'root'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY '你的强密码';
排查空用户和 host 冗余账号
空用户是静默杀手,它不校验密码,但匹配优先级高于所有命名用户:
- 查是否存在:
SELECT user, host FROM mysql.user WHERE user = '' OR user IS NULL; - 删掉它:
DROP USER ''@'localhost';(MySQL 5.7+)或DELETE FROM mysql.user WHERE user = '';(5.6 及更早) - 顺手检查是否有多个 root 账号:
SELECT user, host FROM mysql.user WHERE user = 'root';,确认'root'@'localhost'、'root'@'127.0.0.1'、'root'@'%'是否都按需存在且密码一致 - 宝塔、Docker 或云数据库常默认只开
'root'@'%',但限制远程连接——这时本地用-h localhost就必然失败
跳过权限验证重置密码的实操要点
当完全无法登录时,--skip-grant-tables 是最后手段,但容易踩坑:
- 停服务后,Linux 用
sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables",Windows 改my.ini的[mysqld]段加skip-grant-tables,别直接后台起mysqld --skip-grant-tables &——容易失控 - 免密进库后,**不要**用
UPDATE mysql.user SET authentication_string = 'xxx'手动填哈希值,一律走ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'; - 改完必须
FLUSH PRIVILEGES;,然后还原配置、重启服务——否则插件和 host 规则不会刷新 - 如果
ALTER USER报错说“Unknown column 'password' in 'field list'”,说明你连的是旧版本(5.7 以前),该用SET PASSWORD FOR 'root'@'localhost' = PASSWORD('新密码');
真正麻烦的从来不是改密码,而是改完之后没意识到 MySQL 认证是靠三元组(user, host, plugin)联合决策的。少查一行 SELECT user, host, plugin FROM mysql.user,就可能白调一整天。


















