REVOKE DROP和ALTER权限必须严格匹配原始GRANT的作用域(如ON app_db.*),否则静默失败;需同步回收CREATE权限防绕过,旧连接仍有效须KILL或重连验证。

REVOKE DROP和ALTER必须匹配原始授权范围
执行 REVOKE 不是“覆盖式”操作,它只撤销与当初 GRANT 完全一致的作用域权限。比如用户是用 GRANT DROP, ALTER ON app_db.* TO 'dev'@'%' 授的权,那必须用 REVOKE DROP, ALTER ON app_db.* FROM 'dev'@'%' 才生效;若误写成 ON *.* 或 ON app_db.users,命令会静默成功但实际没撤掉。
常见错误现象:执行完 REVOKE DROP ON *.* FROM 'dev'@'%' 后,仍能成功执行 DROP TABLE app_db.config——本质是权限根本没回收。
- 先查原始授权:
SHOW GRANTS FOR 'dev'@'%',重点看ON后面的数据库/表范围 - 若用户有多个作用域(如同时有
ON app_db.*和ON test_db.*),需分别REVOKE - MySQL 8.0+ 中,
TRUNCATE是独立权限,也得一并REVOKE TRUNCATE ON app_db.* FROM 'dev'@'%'
ALTER权限不能单独撤,要防住“Rename + Create”绕过路径
用户即使没有 DROP 权限,只要持有 ALTER 和 CREATE,就能通过 ALTER TABLE t1 RENAME TO t1_bak; CREATE TABLE t1 (...) 实现逻辑删表。这种绕过方式在生产环境真实存在,且不触发任何 DROP 相关审计。
必须组合回收:
REVOKE DROP, ALTER, CREATE ON app_db.* FROM 'dev'@'%'- 如果只针对某张表(如配置表),用
REVOKE ALTER ON app_db.config FROM 'dev'@'%',但注意ALTER是数据库级权限,无法按单表精确控制 - 检查是否残留高危权限:
SELECT * FROM mysql.role_edges WHERE TO_USER = 'dev' AND TO_HOST = 'host',确认没绑定含ALTER的角色
权限变更后旧连接仍有效,必须主动清理或验证
MySQL 权限变更不会自动同步到已建立的连接。用户只要不退出客户端、不关闭应用连接池里的连接,哪怕你刚执行完 REVOKE,他照样能执行 ALTER TABLE——这是生产环境最常被忽略的点。
- 验证是否真生效:用目标用户登录新会话,执行
ALTER TABLE app_db.users COMMENT 'test',应报错ERROR 1142 (42000): ALTER command denied - 强制清理活跃连接:
KILL CONNECTION <id></id>,或等连接超时断开 - 若用连接池(如 HikariCP、Druid),需重启应用或触发连接重建
MySQL 8.0+ 推荐用角色隔离,而不是长期授高危权限
把 DROP、ALTER 单独拎进一个角色,只在必要时临时启用,比长期绑定到开发账号上更可控。
- 创建角色:
CREATE ROLE 'ddl_admin' - 授权给角色:
GRANT DROP, ALTER ON app_db.* TO 'ddl_admin' - 把角色授给 DBA 账号:
GRANT 'ddl_admin' TO 'dba_user'@'%' - 开发账号默认不激活该角色,避免误触
- 注意:角色默认不自动激活,
SET ROLE 'ddl_admin'只对当前会话有效,关掉终端就失效——这反而是安全优势
真正危险的不是权限收不回,而是忘了角色绑定、残留连接、以及 ALTER + CREATE 这种非显式删表路径。别只盯着 DROP,ALTER 的破坏力在结构变更场景下往往更隐蔽、更难恢复。


















