答案是先确认报错末尾的(using password: yes/no)以区分密码问题与权限问题,再查mysql.user表确认'root'@'%'是否存在,接着用show grants或查mysql.db验证目标库授权,最后注意mysql 8.0权限语法及flush privileges。

先确认是不是密码问题
看到 Access denied for user 'root'@'%' 就重置密码,大概率白忙活。关键看报错末尾有没有 (using password: YES) 或 (using password: NO):
- 有
(using password: YES):密码对了,但权限不足或账号不存在(比如只有'root'@'localhost',没有'root'@'%') - 有
(using password: NO):客户端根本没传密码,可能是连接字符串里漏了?password=xxx或配置项写错成pwd而非password - 完全没有
using password后缀,且报错停在to database 'xxx':说明已通过认证,但mysql.db表里没给这个用户对该库的授权
查清楚当前生效的是哪条用户记录
MySQL 匹配用户时按 (Host, User) 二元组精确匹配,'%' 和 'localhost' 是两条完全独立的账号。执行:
SELECT Host, User, authentication_string FROM mysql.user WHERE User = 'root';
重点看有没有 'root'@'%' 这一行。如果没有,哪怕你用的是远程 IP 连,也会被当成未授权用户拒绝访问库——即使密码正确、服务开着、端口通着。
使用tbot机器ID身份文件配合tsh CLI,通过Teleport访问控制SSH登录托管主机或执行远程命令。
- 如果只有
'root'@'localhost':本地 socket 连接能通,TCP/IP 远程连就会卡在第二阶段(库级权限检查) - 如果只有
'root'@'%':远程能连,但本地用127.0.0.1(走 TCP)能连,用localhost(走 socket)反而可能失败(取决于 MySQL 版本和--skip-name-resolve设置) - 如果两条都有:优先级由 Host 字符串长度决定,
'localhost'比'%'更具体,所以本地连会命中前者
检查目标库的权限是否真正生效
全局权限(GRANT ALL ON *.*)不等于对所有库自动可操作。MySQL 8.0+ 默认启用 sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,...,但更隐蔽的问题出在授权表本身:
- 运行
SHOW GRANTS FOR 'root'@'%';,看输出里有没有包含目标库名的行,例如GRANT USAGE ON `mydb`.* TO 'root'@'%' - 如果只看到
GRANT ALL PRIVILEGES ON *.*,说明是全局权限,应该够用;但如果看到GRANT SELECT ON `otherdb`.*却没提mydb,那就得补授权 - 直接查
mysql.db表:SELECT * FROM mysql.db WHERE User='root' AND Host='%' AND Db='mydb';,结果为空就代表没授权 - 注意:改完
mysql.db表后必须执行FLUSH PRIVILEGES;,否则不生效
容易被忽略的兼容性细节
MySQL 5.7 和 8.0 的权限机制差异不小,尤其在 root 账号初始化和默认 host 绑定上:
- MySQL 8.0 安装后,默认只建
'root'@'localhost',不会自动生成'root'@'%';而某些一键包(如 XAMPP、MAMP)可能反向操作,只开%不留localhost -
GRANT ... IDENTIFIED BY在 8.0 中已弃用,改用ALTER USER ... IDENTIFIED WITH ... BY,但老脚本沿用旧语法会导致授权失败却不报错 - 如果目标库名含下划线、短横或大小写混合,确保授权语句中库名用反引号包裹:
GRANT ALL ON `my-db`.* TO 'root'@'%'; - 某些容器环境(如 Docker 官方镜像)启动时加了
--skip-grant-tables参数,导致权限系统未加载,此时任何GRANT都无效
最常卡住的地方不是不会写 GRANT,而是没意识到 'root'@'localhost' 和 'root'@'%' 是两个账号、mysql.db 表要手动刷新、以及 8.0 对 IDENTIFIED BY 的静默降级处理。










