MySQL撤销DELETE权限必须严格匹配原始授权范围,否则无效;已存在连接不立即生效,需重建连接;多host账户需分别处理;优先考虑禁用或删除账号而非仅撤权限。

直接撤销 DELETE 权限是可行的,但必须匹配当初授权时的范围(*.*、db_name.* 或 db_name.table_name),否则语句执行成功却无效。
REVOKE DELETE 必须和 ON 子句的粒度完全一致
MySQL 不会自动“向上合并”或“向下推导”权限范围。你当初用 GRANT DELETE ON myapp.* TO 'u'@'%' 授的权,就必须用 REVOKE DELETE ON myapp.* FROM 'u'@'%' 撤;如果误写成 REVOKE DELETE ON *.* FROM 'u'@'%',语句能执行(Query OK),但 myapp.* 上的 DELETE 权限还在。
常见错误现象:
-
SHOW GRANTS FOR 'u'@'%'仍显示有DELETE权限 - 用户还能对目标表执行
DELETE,但你确信已执行过REVOKE
实操建议:
- 先查清原始授权:运行
SHOW GRANTS FOR 'u'@'h',看输出里DELETE出现在哪一行、对应哪个ON范围 - 按原样复制
ON部分到REVOKE语句中,只改TO为FROM - 不推荐“保险起见”全范围撤销,比如对一个只在
logdb.logs有DELETE的用户,去执行REVOKE DELETE ON *.*—— 这不会影响它,还可能掩盖配置错误
撤销后权限不立即生效:连接复用是最大盲区
执行 REVOKE DELETE ON myapp.users FROM 'api'@'10.0.2.%' 后,所有已建立的连接(包括应用里的连接池长连接)依然能执行 DELETE,直到断开重连。这不是 bug,是 MySQL 的设计机制:权限检查基于内存中的权限缓存,而该缓存只在连接建立时加载一次。
这意味着:
-
FLUSH PRIVILEGES对已存在连接无效 —— 它只刷新内存权限表,不触达活跃连接 -
SHOW GRANTS查到权限已撤,不代表当前连接立刻受限 - 真正生效依赖连接重建,生产环境需配合应用侧连接池滚动重启或超时驱逐
验证是否真生效的唯一可靠方式:用新连接测试,例如:mysql -u api -h db-host -p -e "DELETE FROM myapp.users LIMIT 1"
多个 host 记录要分别处理,不能漏掉
同一个用户名在 mysql.user 表里可能对应多条记录,比如 'app'@'10.0.2.%' 和 'app'@'localhost' 是两个独立账户,各自拥有独立权限。只对其中一个执行 REVOKE,另一个照旧保留 DELETE。
实操建议:
- 先查全量:运行
SELECT Host,User FROM mysql.user WHERE User = 'app'; - 对每条匹配记录,都执行对应
REVOKE DELETE ON ... FROM 'app'@'host' - 特别注意
'app'@'%'和'app'@'127.0.0.1'—— 它们在网络层等价,但在权限系统里是不同条目
彻底禁用账号比只撤权限更安全
仅撤销 DELETE 权限,用户仍可登录、查询、甚至通过其他方式间接删数据(如 TRUNCATE、DROP TABLE,如果这些权限没一并收回)。若该账号已不再需要,优先考虑锁定或删除:
- 临时禁用(MySQL 5.7.6+):
ALTER USER 'app'@'%' ACCOUNT LOCK; - 永久删除:
DROP USER 'app'@'%';(这会自动清理所有相关权限记录,比手动REVOKE+DELETE FROM mysql.user更干净) - 不要只靠
REVOKE ALL PRIVILEGES, GRANT OPTION留着空账号 —— 它仍能连上,且可能被误授新权限
真正难处理的从来不是怎么写那条 REVOKE 语句,而是确认权限到底从哪来、现在在哪生效、以及谁还在用着那个旧连接。


















