直接改mysql.user表权限混乱,因mysql 8.0+绕过校验、缓存同步与字段协同逻辑,authentication_string与plugin不匹配、host精确匹配失效、flush privileges不校验非法值且不修复关联权限表,导致静默失败。

直接改 mysql.user 表后权限混乱,不是“改错了”,而是 MySQL 8.0+ 的权限系统根本不接受这种操作方式——它绕过了所有校验、缓存同步和字段协同逻辑,写进去的值大概率是非法的,但 MySQL 还是会加载进内存,然后在连接时静默失败。
authentication_string 和 plugin 字段不匹配导致认证失败
MySQL 8.0 默认用 caching_sha2_password 插件,其 authentication_string 必须是合法 SHA256 哈希(形如
$A$...<code>)。手动 UPDATE 时常见错误: <ul> <li>填了明文密码或 MD5 值,MySQL 不报错但认证永远失败</li> <li>只改了 <code>authentication_string</code> </li> </ul></code>,却漏设
plugin = 'caching_sha2_password',导致插件尝试用旧逻辑解哈希
plugin = 'mysql_native_password',但客户端没配 default-authentication-plugin,连接时回退失败Host 匹配失效:你以为改的是 'u'@'%',其实连的是 'u'@'192.168.1.100'
MySQL 在连接时会严格按客户端真实来源 IP 或域名匹配 Host 字段,不会做任何通配推导。常见误判:
- 执行
UPDATE mysql.user SET Host='%' WHERE User='u';→ 批量污染所有u用户,包括u@localhost(socket 连接)和u@127.0.0.1(TCP 连接),而这两者本应隔离 - 客户端解析出的 host 是 IPv6 的
::1,但'u'@'%'不匹配 IPv6 地址,必须显式加'u'@'::1' -
CURRENT_USER()返回'u'@'192.168.1.100',但你只授权了'u'@'192.168.1.%'—— 看似合理,实际因子网掩码或 DNS 解析偏差导致不命中
FLUSH PRIVILEGES 不修复非法数据,反而让问题更隐蔽
FLUSH PRIVILEGES 只是把磁盘数据重载进内存,它不校验字段合法性。一旦你手写的 authentication_string 格式错误、account_locked 值为 'Y' 但 password_last_changed 为空,重载后账号就彻底不可用,且错误日志里往往只报 Access denied,不提示具体哪一列坏了。
- 托管环境(如阿里云 RDS)通常禁用
FLUSH PRIVILEGES,执行直接报ERROR 1227 - 已有连接不受影响,新连接才用新快照 —— 所以你刷完立刻测试,可能连的还是旧会话,误以为“生效了”
- 某些字段(如
password_history、password_lifetime)在 8.0+ 中新增,手改表时若遗漏,会导致密码策略异常但无明确报错
关联权限表未同步更新,造成权限断层
mysql.user 只管全局权限和认证,真正控制库/表/列级访问的是 mysql.db、mysql.tables_priv 等表。直接 UPDATE user 表完全不影响这些表:
- 你给
'u'@'%'加了Super_priv='Y',但mysql.db里该用户对mydb的Select_priv仍是'N',结果仍不能查任何表 - 删用户用
DELETE FROM mysql.user,但mysql.db、mysql.procs_priv里的对应行还在,残留权限可能被其他同名用户意外继承 - GRANT 语句会自动写多张表,手改只能碰一张,天然不一致
最危险的不是改错,是改完发现“好像能连”,但某些操作随机失败、某些客户端可以某些不行——这说明内存里加载了部分合法部分非法的数据,排查时得同时查 CURRENT_USER()、SHOW GRANTS、各权限表字段值,再比对认证插件和客户端配置。绕过标准语句的操作,本质上是在和 MySQL 内核打信息差。











