MySQL中REVOKE DROP和TRUNCATE权限必须严格匹配原始GRANT的ON子句(含库名、表名、反引号、host大小写等),否则静默失效;TRUNCATE本质等价于DROP+CREATE,需同步撤销;旧连接不生效,须重连验证。

REVOKE DROP和TRUNCATE必须显式写出ON子句,否则静默失效
MySQL不会自动推断权限范围,REVOKE语句的ON部分必须与当初GRANT时完全一致:库名、表名、反引号、host地址大小写都不能差。哪怕只多一个空格或少一对反引号,执行REVOKE后提示Query OK,实际权限仍在。
常见错误包括:
-
REVOKE DROP ON mydb.users FROM 'u'@'%'失败,因为当初是用GRANT DROP ON `my-db`.`users`授的权 → 必须带反引号:REVOKE DROP ON `my-db`.`users` FROM 'u'@'%' -
REVOKE TRUNCATE ON *.* FROM 'u'@'%'报错 →TRUNCATE权限不支持全局作用域,只能写成REVOKE TRUNCATE ON mydb.* FROM 'u'@'%'或具体表 - 用户有多个host记录(如
'u'@'localhost'和'u'@'%'),只对其中一个REVOKE,另一个仍保留权限
验证是否真正撤掉:先运行SHOW GRANTS FOR 'u'@'%',确认输出里已无对应DROP或TRUNCATE行;再用新连接测试DROP TABLE mydb.t或TRUNCATE TABLE mydb.t是否报ERROR 1142。
TRUNCATE权限本质是DROP+CREATE,不能“只给清空不给删表”
MySQL没有独立的TRUNCATE权限。当你执行GRANT TRUNCATE ON db.t TO 'u'@'%',底层等价于授予DROP和CREATE——用户不仅能清空数据,还能直接DROP TABLE、重建同名表、绕过外键或触发器。
所以收回TRUNCATE不是可选项,而是必须项:
- 若当初授过
TRUNCATE,必须同步REVOKE DROP, CREATE ON db.t FROM 'u'@'%' - 若用户有
ALL PRIVILEGES ON db.*,先REVOKE ALL PRIVILEGES ON db.* FROM 'u'@'%',再重授安全权限(如SELECT, INSERT, UPDATE) - MySQL 8.0+中
TRUNCATE需显式授权,但旧版本依赖DROP权限,因此跨版本环境更要统一检查DROP是否残留
仅撤DROP不够防误删,DELETE和ALTER也得一并控制
用户没DROP权限,仍可能通过其他路径破坏数据:
-
DELETE FROM users(无WHERE)能全表清空,且比TRUNCATE更隐蔽(走binlog、触发器生效)→ 必须REVOKE DELETE ON db.users FROM 'u'@'%' -
ALTER TABLE users RENAME TO users_bak+CREATE TABLE users可间接实现“删表重建” → 需REVOKE ALTER, CREATE ON db.users FROM 'u'@'%' - 持有
SUPER或SYSTEM_VARIABLES_ADMIN权限的用户可关sql_log_bin绕过所有DDL限制 → 必须查SHOW GRANTS,发现即REVOKE SUPER ON *.* FROM 'u'@'%'
生产环境建议默认关闭DELETE的无条件使用:在会话级启用sql_safe_updates=ON,强制DELETE必须带WHERE或LIMIT。
权限变更后旧连接不生效,必须刷新或重启连接池
REVOKE只更新mysql系统表,已建立的连接(包括应用连接池里的长连接)仍用旧权限快照。你看到SHOW GRANTS已变,但应用代码仍能成功DROP,大概率是这个原因。
解决办法只有两个:
- 让应用主动断开重连(如滚动重启服务、清空连接池)
- 等连接超时自动驱逐(风险高,不推荐)
没有FLUSH PRIVILEGES能立即刷新已有连接的权限缓存——这是MySQL权限模型的硬性限制,也是最容易被忽略的一环。


















