必须同时撤销drop和truncate权限,mysql 8.0+需revoke drop, truncate on mydb.* from 'u'@'%';5.7及以前需revoke drop, delete;作用域须与原始grant完全一致,且需重连生效。

REVOKE DROP 和 TRUNCATE 必须分开写,不能省略任一权限
MySQL 的 DROP 和 TRUNCATE 是两个独立权限,即使都用于删数据,也不能靠撤掉其中一个就防住全部。比如只执行 REVOKE DROP ON mydb.* FROM 'u'@'%';,用户仍可用 TRUNCATE TABLE t; 清空整表——在 MySQL 8.0+ 中它需要显式授予的 TRUNCATE 权限;旧版本则依赖 DROP + DELETE,但行为不一致,必须按目标版本分别处理。
实操建议:
- MySQL 8.0+:必须同时撤销
DROP和TRUNCATE,例如:REVOKE DROP, TRUNCATE ON mydb.* FROM 'u'@'%'; - MySQL 5.7 或更早:
TRUNCATE不是独立权限,实际由DROP+DELETE共同支撑,所以至少要加REVOKE DROP, DELETE ON mydb.* FROM 'u'@'%'; - 如果当初授权时还包含
GRANT OPTION,记得额外补一句:REVOKE GRANT OPTION ON mydb.* FROM 'u'@'%';(ALL PRIVILEGES不含它)
作用域必须和原始 GRANT 完全一致,否则不生效
常见错误是写 REVOKE DROP ON *.* FROM 'u'@'%'; 去撤一个原本授在 mydb.* 上的权限——语法合法,但 MySQL 找不到匹配项,静默失败。它不是模糊匹配,而是严格比对当初 GRANT 语句里的数据库名、表名、主机名。
检查原始授权最直接的方式是运行:SHOW GRANTS FOR 'u'@'%';,看输出里具体是哪一行 GRANT ... ON ...。然后照抄那个 ON 后面的部分,一个字符都不能改。
容易踩的坑:
-
'u'@'localhost'和'u'@'%'是两个账号,不能混用 -
mydb.t1和mydb.*不等价,撤单表权限不会影响其他表 - 用通配符如
mydb.%授权的(MySQL 8.0 支持库名通配),回收时也得写成mydb.%,不能简写为mydb.*
撤销后已建立的连接不受影响,必须重连才生效
REVOKE 只更新权限系统表(如 mysql.db),不中断当前会话。应用里用连接池维持的长连接,哪怕你刚执行完 REVOKE,接下来 10 分钟内照样能执行 DROP TABLE——只要那条连接没断开。
验证是否真正生效,不能查旧连接里的 SHOW GRANTS,而要新开一个终端执行:mysql -u u -p -e "SHOW GRANTS;"。返回结果里若还有 DROP 或 TRUNCATE,说明要么没撤对,要么没重连。
补充提醒:
- 全局权限(如
SELECT ON *.*)变更,只在下次新连接时生效 - 库级权限(如
SELECT ON mydb.*)需执行USE mydb;后才刷新 - 表级权限变更,从下一条语句开始生效,但
DROP/TRUNCATE这类 DDL 本身不走缓存,所以一般立刻受限(前提是连接已重连)
别漏掉 ALTER 和 CREATE,否则用户可能绕过 DROP 限制
只撤 DROP 权限并不等于防住了删表。用户可以用 ALTER TABLE t RENAME TO t_bak; + CREATE TABLE t AS SELECT 1; 实现等效清空,只要他还持有 ALTER 和 CREATE 权限。
如果你的真实目标是“防止表结构或数据被意外清除”,就得组合控制:
- 撤结构类操作:
REVOKE DROP, ALTER, CREATE ON mydb.* FROM 'u'@'%'; - 撤数据类操作:
REVOKE DELETE, TRUNCATE ON mydb.* FROM 'u'@'%'; - 特别注意:如果用户有
SUPER或SYSTEM_VARIABLES_ADMIN,他甚至能关掉sql_log_bin直接执行 DDL,REVOKE DROP对这种权限完全无效
权限回收不是一锤子买卖,得结合业务场景判断攻击面——比如运维账号要不要保留 ALTER?报表账号有没有必要留 CREATE TEMPORARY TABLES?这些细节往往比 REVOKE 语句本身更关键。











