根本原因是mysql的drop权限为数据库级且不可自动失效,一旦授予all privileges即长期有效;必须通过revoke drop on .、新建账号禁用all privileges、或mysql 8.0+角色隔离三种机制卡死,同时强化连接环境隔离防连错库。

开发账号为什么能执行 DROP DATABASE
根本原因不是权限开太多,而是没显式回收——MySQL 的 DROP 权限是数据库级的,一旦被授予过 ALL PRIVILEGES ON *.* 或 ALL PRIVILEGES ON `app_%`.*,哪怕后续只给 SELECT, INSERT,DROP 依然有效。用 SHOW GRANTS FOR 'dev_user'@'%' 一查,常能看到残留的 GRANT DROP ON `prod_db`.*。
必须禁用 DROP 权限的三种实操方式
不能靠“信任开发不手抖”,得靠机制卡死:
- 对已有账号立刻回收:
REVOKE DROP ON *.* FROM 'dev_user'@'%'; FLUSH PRIVILEGES;(注意:不能写成REVOKE DROP ON `mydb`.*后再删库——万一库名记错就白操作) - 新建账号时彻底绕过:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES ON `dev_%`.* TO 'dev_user'@'%';,绝不用ALL PRIVILEGES - MySQL 8.0+ 推荐用角色隔离:
CREATE ROLE 'ddl_role'; GRANT DROP ON `staging_%`.* TO 'ddl_role';,再只把ddl_role授给 DBA 账号,且要求其每次执行前手动SET ROLE 'ddl_role';
DROP DATABASE 命令本身怎么加防护
MySQL 原生不支持 BEFORE DROP DATABASE 触发器,但你可以从客户端和中间层补位:
- 命令行客户端强制启用安全模式:
mysql --safe-updates -u dev_user -p,它虽不拦DROP DATABASE,但能防止全表UPDATE/DELETE,降低连带风险 - 在连接池或代理层(如 ProxySQL、ShardingSphere)配置 SQL 拦截规则,匹配正则
^DROP\s+DATABASE\s+[^\s;]+;?$并直接拒绝 - 所有自动化脚本开头加校验:
SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'target_db';,非 1 则退出;再加一句SELECT USER(), CURRENT_USER();确认登录账号不是root@%
真正防住误删的关键不在权限,而在连接环境
90% 的误删发生在开发人员连错库——本该连 mysql-dev.example.com,却因硬编码或配置错误连了 mysql-prod.example.com。这时候权限再细也没用。
- 禁止代码里出现
host='10.10.10.10'或host='localhost',全部改用语义化域名:DB_HOST=mysql-dev/DB_HOST=mysql-prod - CI/CD 流水线中禁止变量名含
prod、production,GitHub Actions 可加if: contains(env.DEPLOY_ENV, 'prod')拒绝运行 - 终端提示符或 IDE 标签页必须显示当前环境,比如 Bash 的
PS1加$(echo $DB_HOST | cut -d'-' -f2)
权限回收只是第一道门,连接隔离才是那把锁芯——没锁好门,再厚的门板也挡不住人自己开门进去。











