show grants 是唯一可信的权限检查方式,它直接反映真实生效权限;查 mysql.user 表的 drop_priv 字段不可靠,因其仅记录历史设置而非当前生效状态,且忽略库表级细粒度权限与角色继承权限。

SHOW GRANTS 是唯一可信的权限检查方式
别查 mysql.user 表里的 Drop_priv 字段——它只记录“曾经设为 Y”,不反映当前生效权限。比如用户在 order_db 上被 REVOKE DROP ON order_db.* 过,Drop_priv 仍可能是 Y,因为全局 DROP 没动,而 MySQL 优先匹配更细粒度规则。
正确做法是执行:SHOW GRANTS FOR 'app_user'@'10.20.30.%'。输出里若含 GRANT DROP ON `order_db`.*,才说明真有该权限;若只有 GRANT USAGE ON *.*,说明只剩连接权。
如果用户通过角色获得权限,还要查:SELECT * FROM mysql.role_edges WHERE TO_USER = 'app_user' AND TO_HOST = '10.20.30.%',再对每个角色执行 SHOW GRANTS FOR 'role_name'@'%' 确认是否含 DROP。
REVOKE DROP 必须严格镜像原始 GRANT 的作用域
MySQL 的 REVOKE 不是“删掉所有叫 DROP 的权限”,而是“撤回当初用完全相同范围授予的那一项”。作用域不一致,命令会静默失败——不报错,也不生效。
- 原始授权是:
GRANT DROP ON order_db.* TO 'app_user'@'10.20.30.%',回收就必须写:REVOKE DROP ON order_db.* FROM 'app_user'@'10.20.30.%' - 写成
REVOKE DROP ON *.*:语法合法,但不生效 - 写成
REVOKE DROP ON order_db.orders:只撤单表,漏了库内其他表 - 用户同时有
ON order_db.*和ON *.*两个DROP,需分别REVOKE
注意:DROP TABLE 权限在 MySQL 中是数据库级(DATABASE LEVEL),不支持按表粒度回收,REVOKE DROP ON order_db.users 会直接报错 ERROR 1147。
仅撤 DROP 不足以防误删,TRUNCATE 和 DELETE 也得同步处理
DROP TABLE 删结构,TRUNCATE TABLE 清空数据且不走 binlog,DELETE FROM table 同样能全表清空——三者权限彼此独立,缺一不可。
MySQL 8.0+ 中 TRUNCATE 是单独权限(归类 DDL),旧版则依赖 DELETE + DROP。所以防误删必须组合回收:
REVOKE DROP, TRUNCATE, DELETE ON order_db.* FROM 'app_user'@'10.20.30.%'
如果原始授权含 GRANT OPTION,还得单独加一句:REVOKE GRANT OPTION ON order_db.* FROM 'app_user'@'10.20.30.%'——ALL PRIVILEGES 不包含它。
权限变更后旧连接仍有效,验证必须新开会话
REVOKE 执行成功后,服务端权限立即更新,但已建立的连接(包括应用连接池里的长连接)仍使用连接建立时缓存的权限快照。
验证是否真生效的唯一方式是:
- 新开一个连接:
mysql -u app_user -p -h db-host -P 3306 - 登录后立刻执行:
SHOW GRANTS FOR 'app_user'@'10.20.30.%',确认输出里已无DROP行 - 再试
DROP TABLE order_db.test,应报ERROR 1142 (42000): DROP command denied
别信旧终端里的测试结果——哪怕你刚执行完 REVOKE,只要没断开重连,它照样能删表。这是生产环境最常被忽略的点,也是权限回收失效的头号原因。











