必须从权限体系入手禁止 drop,因客户端拦截无效;drop 权限仅支持数据库级(on mydb.)或全局(on .),需用 revoke drop on mydb. 回收,并避免授予 all privileges 或 grant option。

不能靠客户端拦截或触发器真正禁止 DROP,必须从权限体系入手——只要用户有 DROP 权限,无论用 mysql 命令行、mycli 还是 JDBC,执行 DROP TABLE 都会成功。
MySQL 的 DROP 权限是数据库级,无法按表回收
你不能写 REVOKE DROP ON mydb.mytable FROM 'user'@'%',MySQL 会报错 ERROR 1147 (42000): There is no such grant defined for user。DROP 权限只作用于数据库层级(ON mydb.*)或全局(ON *.*),这是权限模型的硬限制。
- 检查用户是否已有 DROP:运行
SHOW GRANTS FOR 'user'@'%',看输出里有没有GRANT DROP ON `mydb`.* - 回收方式只能是数据库级:
REVOKE DROP ON `mydb`.* FROM 'user'@'%' - 如果用户是通过角色获得权限,需对角色执行
REVOKE DROP ON `mydb`.* FROM 'role_name',再执行SET DEFAULT ROLE刷新 - 注意:
FLUSH PRIVILEGES在使用REVOKE时通常不需要,它只在直改mysql.user表时才强制要求
别给业务用户授 ALL PRIVILEGES 或 GRANT OPTION
很多误删源于初始授权太宽。一旦授予 ALL PRIVILEGES ON mydb.*,后续单条 REVOKE DROP 虽有效,但容易漏检;而 GRANT OPTION 允许用户自行加回 DROP,等于白设防。
- 建用户时直接最小化授权:
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'appuser'@'%'—— 不含DROP、ALTER、CREATE - 禁用
GRANT OPTION:REVOKE GRANT OPTION ON *.* FROM 'appuser'@'%'(必须用ON *.*,写成库级会报错) - 避免用
CREATE USER ... IDENTIFIED BY '',空密码+通配 host 是高危起点 - MySQL 8.0+ 推荐用角色统一管理:
CREATE ROLE 'app_rw'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_rw';,再把角色赋给用户
客户端或中间件拦截只是辅助,且有明显缺陷
原生 mysql 客户端不支持关键字过滤;所谓“拦截”实际是预扫描,对多行语句、注释包裹(如 /* DROP */ TABLE)、变量拼接(SET @s = 'DROP TABLE'; PREPARE)完全无效。
- 用
mycli可插件式拦截:sql.lower().startswith('drop'),但仅限交互式输入,不拦脚本或应用层 SQL - Shell wrapper 示例:
grep -i '^[[:space:]]*drop[[:space:]]' && echo "DROP blocked" &>2 && exit 1,同样绕过简单 - ProxySQL 或 ShardingSphere 可配置正则规则拦截
^DROP TABLE,但需额外运维成本,且不拦DROP DATABASE等跨库操作 - JDBC 中用
PreparedStatement对DROP完全无效——它只防注入,不防合法 DDL
生产环境必须搭配 binlog + 结构导出兜底
DROP 是 DDL,隐式提交,InnoDB 事务日志不记录页级删除,误执行后基本不可逆。权限控制和拦截都可能失效,真正能救命的是可追溯、可还原的机制。
- 确保
log_bin = ON且binlog_format = ROW,定期验证mysqlbinlog是否能解析 - 每天用
mysqldump --no-data --skip-triggers导出表结构,存 Git 或对象存储,比依赖 binlog 更可靠(RESET MASTER会清空 binlog) - 禁用
sql_log_bin = OFF(会跳过 binlog 记录),DBA 会话也应保持开启 - 关键配置表(如
config_settings)建议单独建库并限制访问 IP,物理隔离比逻辑权限更直接
最易被忽略的一点:DBA 自己的账号也别长期持有 DROP ON *.*。用角色临时启用(SET ROLE 'drop_admin'),会话结束即失效——这不是麻烦,是把“手抖”挡在执行前的最后一道门。











