revoke执行成功不等于权限消失,关键在旧连接未断、账号(host)不匹配、权限层级(global/db/table/column/role/proxy)未全覆盖;必须查current_user()、杀掉活跃连接、逐层验证并清理残留权限。

REVOKE 执行成功不等于权限消失,关键在“谁的连接、哪个账号、哪层权限”没断干净。
确认当前会话用的是不是被撤销权限的那个账号
MySQL 权限校验基于 (user, host) 二元组,而 'u'@'localhost' 和 'u'@'127.0.0.1' 是两个完全独立的账号。你执行了 REVOKE SELECT ON mydb.* FROM 'u'@'%',但客户端实际连的是 'u'@'127.0.0.1' 或 'u'@'::1',那撤销就根本没触达目标行。
- 查所有匹配用户:
SELECT Host, User FROM mysql.user WHERE User = 'u'; - 看当前会话真实身份:
SELECT USER(), CURRENT_USER();—— 前者是“你怎么登录的”,后者才是 MySQL 实际认证通过的账号(即真正生效权限所属) - IPv6 的
::1必须单独授权,%不覆盖它
验证撤销是否真作用到了目标权限层级
REVOKE 不报错也不保证撤掉任何东西:如果原权限压根不存在,或者对象范围不一致(比如当初授的是 mydb.t1,你却撤 mydb.*),命令照样返回 Query OK,但什么都没发生。
- 先查原始授权:
SHOW GRANTS FOR 'u'@'host';,逐字比对ON后面的部分:库名、表名、主机名、权限名(SELECT区分大小写) - 注意
ALL PRIVILEGES不包含GRANT OPTION,必须显式写REVOKE ALL PRIVILEGES, GRANT OPTION ON ... - 细粒度权限不会被数据库级
REVOKE清除:mysql.columns_priv、mysql.tables_priv、mysql.proxies_priv都得单独查和清理
检查旧连接是否还在扛着权限快照
MySQL 在连接建立时把权限固化进会话内存,之后无论你 REVOKE 多少次、FLUSH PRIVILEGES 多少遍,该连接都继续用旧快照。这不是 bug,是设计如此。
- 查活跃连接:
SELECT ID, USER, HOST, COMMAND, TIME FROM information_schema.PROCESSLIST WHERE USER = 'u'; - 杀掉旧连接:
KILL CONNECTION 123;(别漏CONNECTION,否则只中断语句) - 应用若用连接池(如 HikariCP),需主动驱逐或等空闲超时;GUI 工具(Navicat/DBeaver)常复用连接,删配置再新建才有效
- 新连接验证:用
mysql -u u -p -h 127.0.0.1这种明确指定 host 的方式重连
排查残留权限来源(最容易被忽略)
你以为清空了 mysql.db 就万事大吉?攻击面远不止这里。
- 角色继承:
SELECT * FROM mysql.role_edges WHERE TO_USER = 'u' AND TO_HOST = 'host';—— 如果用户属于某个带SELECT的角色,SHOW GRANTS会显示该权限,但你只撤了用户自身权限 - 全局权限残留:
SELECT Select_priv FROM mysql.user WHERE User = 'u' AND Host = 'host';—— 若为Y,用户仍能查所有库的表名甚至执行SELECT(取决于版本) - 代理权限:
SELECT * FROM mysql.proxies_priv WHERE User = 'u' AND Host = 'host';—— 代理关系可能绕过常规权限检查 - public 角色(MogDB/openGauss 行为):
REVOKE CONNECT ON DATABASE mydb FROM PUBLIC;可能才是关键
权限回收的成败不在 REVOKE 是否执行,而在旧连接是否彻底退出、目标账号是否精准匹配、所有权限层级(global/db/table/column/role/proxy)是否全部覆盖。漏掉任意一层,就等于留了一扇没锁的门。











