根本原因是mysql启动时将权限表加载进内存缓存,后续验证均查内存快照;update mysql.user仅修改磁盘数据,不触发缓存更新,必须flush privileges强制重载(5.7及以前),而8.0+手动改表易出错且flush可能无效。

MySQL 修改 mysql.user 表后权限不生效,根本原因不是“改错了”,而是 MySQL 启动时就把权限表加载进内存缓存,后续所有连接都查这份快照——你改的是磁盘文件,内存里的旧数据没变。
为什么直接 UPDATE mysql.user 不触发自动刷新
MySQL 的权限系统设计上就区分了“数据层”和“运行时缓存层”。UPDATE mysql.user 是纯 DML 操作,绕过了权限校验与缓存管理逻辑,不会调用内部的 ACL 重载机制。它只写磁盘,不通知内存更新。
- 5.7 及以前:必须配
FLUSH PRIVILEGES才能强制从磁盘重载 - 8.0+:字段结构已变(比如
authentication_string格式依赖plugin),手动更新极易写错;即使执行FLUSH PRIVILEGES,也可能因校验失败而静默忽略变更 - 托管环境(如阿里云 RDS)通常禁用
FLUSH PRIVILEGES,直接报ERROR 1227
GRANT 之后还执行 FLUSH PRIVILEGES 是多余操作
GRANT、REVOKE、ALTER USER 这些标准语句在执行时会自动同步磁盘与内存 ACL 缓存,加 FLUSH PRIVILEGES 不报错但完全无效——它既不加速生效,也不修复问题。
- 如果你发现“加了才生效”,大概率是顺手重连了,真正起作用的是
QUIT后的新连接 - 应用代码或部署脚本里写
GRANT; FLUSH PRIVILEGES;,建议删掉后半句,避免误导和兼容性风险 - 某些旧版客户端(如老 Navicat)连上后权限不对,不是缓存没刷,而是认证插件不匹配,
FLUSH PRIVILEGES压根救不了
权限生效时机取决于粒度,不是统一刷新
MySQL 对不同层级权限做了差异化生效控制,不能一概而论“改完就该能用”:
- 表级权限(
GRANT SELECT ON db.t TO 'u'@'h'):下次执行对该表的查询就生效,当前连接内可用 - 数据库级权限(
GRANT SELECT ON db.* TO 'u'@'h'):执行USE db后立即生效,无需重连 - 全局级权限(
GRANT DROP ON *.* TO 'u'@'h'):仅对新连接生效,已有连接必须QUIT后重登 - 连接池场景下,旧连接永远拿不到新全局权限,需配置
connection-init-sql=SELECT 1或定期淘汰空闲连接
排查时最容易被忽略的三个点
很多“权限没生效”的问题,其实跟缓存无关,而是匹配或验证环节出了偏差:
-
SELECT USER(), CURRENT_USER();—— 前者是你“怎么连的”,后者才是 MySQL “认你是谁”。'u'@'localhost'和'u'@'127.0.0.1'是两个完全不同的账号,% 不覆盖 ::1(IPv6) - 别只信
SHOW GRANTS FOR 'u'@'h',它可能来自角色继承或缓存;要实操验证:SELECT * FROM t;或USE db;看是否真报错 - 检查
plugin字段:SELECT User, Host, plugin FROM mysql.user WHERE User = 'u';。若为caching_sha2_password但客户端不支持,连接成功但权限校验阶段静默失败
最危险的操作是:在没确认 --skip-grant-tables 是否启用的情况下反复刷缓存——此时所有权限校验都被跳过,FLUSH PRIVILEGES 完全无效,你还以为是命令没跑对。











