先确认报错中'user'@'host'是否真实存在于mysql.user表,如'root'@'127.0.0.1'需精确匹配,localhost/127.0.0.1/%互不等价;再查plugin是否为caching_sha2_password导致旧客户端不兼容,须改用mysql_native_password并执行FLUSH PRIVILEGES。

查清报错里那个 'user'@'host' 到底存不存在
错误信息里写的 host,就是 MySQL 实际在找的账号。比如报 Access denied for user 'root'@'127.0.0.1',你就得去查 mysql.user 表里有没有这一行,而不是只看 'root'@'localhost'。
执行这条语句确认:
SELECT User, Host FROM mysql.user WHERE User = 'root';
如果输出里只有 root@localhost,但你用的是 mysql -h 127.0.0.1 -u root -p,那必然失败——TCP 连接匹配的是 'root'@'127.0.0.1',不是 Unix socket 的 'root'@'localhost'。
- 常见错配:localhost / 127.0.0.1 / % / 具体 IP,三者互不等价
- Navicat 默认存
'u'@'%',命令行写'u'@'127.0.0.1'就查不到 - 远程连接时,
'u'@'192.168.1.100'和'u'@'192.168.1.%'也不同
确认认证插件是否被客户端支持
MySQL 5.7+ 默认用 caching_sha2_password,但很多老工具(PHP MySQLi、Python MySQLdb、某些监控 agent)只认 mysql_native_password。密码没错也会报 Access denied。
先查当前插件:
SELECT plugin FROM mysql.user WHERE User = 'root' AND Host = 'localhost';
如果不是 mysql_native_password,就强制改掉:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
- 不能用
UPDATE mysql.user SET authentication_string = ...—— 字段已废弃,且不会生效 - 改完必须立刻执行
FLUSH PRIVILEGES;,否则新设置不加载 - 创建新用户时也得显式指定:
CREATE USER 'u'@'%' IDENTIFIED WITH mysql_native_password BY 'pwd';
检查当前用户有没有 GRANT OPTION
用 root 登录不代表你能给别人授权。MySQL 8.0+ 默认不给 'root'@'localhost' 自带 WITH GRANT OPTION,所以 GRANT 命令会静默失败或报 Access denied。
查自己权限:
SHOW GRANTS FOR CURRENT_USER();
如果输出里没有 WITH GRANT OPTION,补上它:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;
- MySQL 8.0+ 不允许在
GRANT语句里写IDENTIFIED BY,必须拆成CREATE USER+GRANT两步 -
GRANT执行成功 ≠ 权限落地,必须验证目标用户能否真正连上并执行操作 - 执行完记得
FLUSH PRIVILEGES;,尤其在脚本化部署中更稳妥
验证权限是否真生效,别只信 mysql.user 表
执行完 GRANT 后,用目标用户从对应 host 连接,再运行:
SHOW GRANTS FOR 'u'@'h';
输出必须含你刚授的权限,而不是只有 USAGE。如果没看到,说明权限没落库。
- 细粒度权限(库级、表级)不全存在
mysql.user表里,SELECT * FROM mysql.user看不到它们 - MySQL 5.7+ 权限分散在
mysql.db、mysql.tables_priv等系统表中 - 如果
SHOW GRANTS为空或报错,可能是权限表结构损坏,不是语法问题
实际排查时,最容易被跳过的是 host 匹配和插件兼容性这两层。很多人反复重置密码、反复 GRANT,却没注意到错误里写的 host 根本没建过,或者客户端压根不支持当前认证方式。


















