必须严格比对报错中'user'@'host'与mysql.user表记录是否完全一致,包括大小写、空格及通配符;'root'@'localhost'和'root'@'127.0.0.1'被视为两个独立账号,不可互替。

查清报错里写的到底是哪个'user'@'host'
错误信息末尾的 'root'@'127.0.0.1' 或 'app'@'192.168.10.5' 就是 MySQL 实际拒绝的对象——它不会做任何归一化或 fallback。你连的是这个,就得在 mysql.user 表里找到**完全一致**的一行。
常见误判点:
- 以为
'root'@'localhost'能代替'root'@'127.0.0.1':前者走 Unix socket,后者走 TCP,MySQL 当作两个账号 - 用
mysql -h 127.0.0.1连,却只查了SELECT User, Host FROM mysql.user WHERE User = 'root',漏看Host列是否真有127.0.0.1 - 云数据库(如阿里云 RDS)默认不建
'root'@'localhost',只认'root'@'%'或具体白名单 IP,但你连的是内网地址,%却不匹配 IPv6 的::1
确认 mysql.user 表中是否存在精确匹配的记录
登录 MySQL(用已有权限账号),执行:
SELECT User, Host, plugin, authentication_string IS NOT NULL AS has_pwd FROM mysql.user WHERE User = 'root';
重点核对三件事:
- 输出中是否有和报错里 **一模一样** 的
Host值(包括大小写、空格、通配符) -
authentication_string是否为NULL或空字符串(using password: NO时尤其关键) -
plugin字段是否为auth_socket(Ubuntu/Debian 默认值,不校验密码)或caching_sha2_password(老客户端可能不支持)
补账号或改 Host 必须用 CREATE USER + GRANT,不能 UPDATE
直接 UPDATE mysql.user SET Host = '%' 是无效操作,MySQL 8.0+ 已弃用该字段写法,且不触发权限重载。
对设计或页面进行 WCAG 2.1 AA 无障碍审计。触发关键词:“audit accessibility”、 “check a11y”、 “is this accessible?” 或在审查设计时使用。
正确做法:
- 补一个新 host:比如你要用
mysql -h 192.168.1.100 -u app连,但表里只有app'@'192.168.1.%',那就得加一行:CREATE USER 'app'@'192.168.1.100' IDENTIFIED WITH mysql_native_password BY 'pwd'; - 授权必须显式:
GRANT SELECT ON mydb.* TO 'app'@'192.168.1.100';,不能只靠GRANT ... TO 'app'@'192.168.1.%'自动覆盖 - 改完立刻
FLUSH PRIVILEGES;,否则新记录不生效(CREATE USER和GRANT通常自动生效,但直接改表或某些插件切换后必须刷)
别忽略 bind-address 和网络层拦截
即使 'user'@'host' 完全匹配、密码正确、插件兼容,只要 TCP 请求根本没进 mysqld,最终也报 Access denied(不是 Connection refused)。
验证方式:
- 运行
ss -tlnp | grep :3306,输出里必须含*:3306或具体外网 IP,若只有127.0.0.1:3306,说明bind-address = 127.0.0.1没放开 - Docker 容器内连宿主机 MySQL,
host往往是容器网关(如172.17.0.1),不是localhost,得在mysql.user里建对应记录 - 云服务器安全组 / 防火墙未放行 3306 入方向,请求被丢弃前 MySQL 根本不参与鉴权
真正难排查的,是 Host 字符串字面匹配这件事——MySQL 不解析 DNS、不展开 CIDR、不转换 IPv4-mapped IPv6,你看到什么,它就比对什么。别靠经验猜,查表、试连、看 SHOW GRANTS FOR CURRENT_USER(); 输出,才是唯一可靠路径。

















