应用账号能 DROP DATABASE 并非配置遗漏,而是权限叠加未清干净,REVOKE 必须与 GRANT 范围完全一致才生效,且需检查角色继承、底层 mysql.db 表及所有非业务库权限。

为什么应用账号能 DROP DATABASE 从来不是配置遗漏,而是权限叠加没清干净
新建用户后直接 GRANT ALL ON app_db.*,哪怕后续 REVOKE DROP,也大概率留有残留——因为 MySQL 权限是叠加生效的,REVOKE 必须和之前 GRANT 的范围完全一致才能撤回。更常见的是:某次临时调试给了 ALL PRIVILEGES ON *.*,之后只 REVOKE 了业务库权限,却忘了系统库和全局权限,DROP DATABASE 依然有效。
检查 DROP 权限是否真实存在,别信 SHOW GRANTS 的表面输出
SHOW GRANTS FOR 'app_user'@'10.20.%' 可能不显示 DROP,但该用户仍能执行 DROP DATABASE——尤其在 MySQL 8.0+ 启用角色后,权限可能来自角色继承。必须查底层:
- 运行
SELECT User, Host, Db, Drop_priv FROM mysql.db WHERE User = 'app_user' AND Host = '10.20.%' AND Db = 'app_db';,确认Drop_priv是N - 查角色绑定:
SELECT * FROM mysql.role_edges WHERE TO_USER = 'app_user' AND TO_HOST = '10.20.%';,再对每个角色执行SHOW GRANTS FOR 'role_name'@'%' - 重点盯
GRANT DROP ON `app_db`.*这类语句,只要存在,就等于开了删库门
回收 DROP 权限必须走数据库级,且不能依赖通配符
REVOKE DROP ON app_db.* FROM 'app_user'@'10.20.%' 是唯一可靠方式;REVOKE DROP ON app_db.orders 会报错,MySQL 不支持表级 DROP 权限回收。注意几个关键点:
- 必须显式指定数据库名,不能写成
REVOKE DROP ON *.*——它不作用于DROP DATABASE,只影响DROP TABLE等操作 - 如果用户曾被授予
ALL PRIVILEGES ON *.*,得先REVOKE ALL PRIVILEGES ON *.*,再逐库GRANT,否则REVOKE DROP ON app_db.*无效 - MySQL 5.7 不支持动态权限,
DROP是静态权限,回收后无需FLUSH PRIVILEGES;8.0+ 同理,但要注意角色需SET ROLE激活才生效
真正防住误删,得靠“权限控制 + 机制拦截”双层落地
权限只是第一道门,DROP DATABASE 执行后无法靠 binlog 回滚(InnoDB 不记录 DDL 的完整 undo),所以不能只靠 REVOKE。
- 生产 MySQL 启动时加
--skip-show-database并设sql_safe_updates=1,虽不拦DROP,但能阻断无条件DELETE/UPDATE,降低连带风险 - 在 ProxySQL 或 ShardingSphere 层配置 SQL 拦截规则,匹配
^DROP[[:space:]]+DATABASE直接拒绝,比数据库层更前置 - 所有线上
DROP操作必须走发布平台审批流,脚本中硬编码DROP语句需触发人工复核,不能自动执行
最常被忽略的一点:DROP DATABASE 不需要目标库存在,也不校验当前连接是否在该库内——哪怕你只 GRANT 了 app_db,用户仍可 DROP DATABASE other_db。所以权限回收必须覆盖所有非业务库,包括 mysql、information_schema 等默认库的 DROP 权限,一个都不能漏。


















