必须同时设置read_only=1和super_read_only=1才能真正禁止写入,因read_only对SUPER权限用户无效;需检查变量作用域、持久化配置及MySQL版本兼容性。

直接禁止用户执行 DELETE 和 DROP,靠 SQL 拦截或客户端过滤不可靠,真正有效的做法是权限收束 + 从库只读加固。其他手段要么绕过容易,要么治标不治本。
只授必要权限,明确不给 DELETE 和 DROP
MySQL 的权限模型里,DELETE 和 DROP 是独立权限项,不授予就不会有对应能力。关键不是“怎么拦”,而是“一开始就不给”。
-
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'appuser'@'%'—— 这样用户能查、能插、能改,但删不了行、删不了表 - 绝对避免
GRANT ALL PRIVILEGES ON mydb.*,它隐含DELETE和DROP - 如果用户已有冗余权限,先
REVOKE DELETE, DROP ON mydb.* FROM 'appuser'@'%',再FLUSH PRIVILEGES -
DROP权限作用于数据库级别(ON db_name.*)或全局(ON *.*),删单表靠的是DROP TABLE权限,必须单独收回
从库启用 super_read_only 防止手动 DELETE
对只读从库来说,super_read_only=on 是最硬核的防护:它让连 SUPER 用户都无法执行 DELETE、UPDATE、DROP 等写操作,但主从复制不受影响。
- 编辑从库配置文件
/etc/my.cnf,加入:[mysqld] read_only=on super_read_only=on
- 重启生效:
systemctl restart mysqld - 验证:
SELECT @@super_read_only;返回1即生效 - 此时执行
DELETE FROM t;会报错:ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option...
别信 PreparedStatement 或客户端关键词过滤
很多人以为用 PreparedStatement 就能防 DROP,其实完全无效——它只防 SQL 注入,不拦合法 DDL。原生 mysql 客户端也不支持语句拦截。
-
executeUpdate("DROP TABLE t")在 JDBC 中照样成功,只要账号有权限 - shell wrapper 用
grep -i '^drop '拦截,对/* DROP */ TABLE、多行语句、预处理变量拼接完全失效 - mycli 插件或 ProxySQL 的
mysql-acl规则可做补充,但不能替代权限控制——它们都属于“防君子不防小人”的辅助层
高危权限必须全局收回
CREATE USER 和 GRANT OPTION 是权限扩散源头,哪怕只在某个库上授了 DROP,用户也可能通过提权绕过限制。
-
REVOKE CREATE USER ON *.* FROM 'devuser'@'%'—— 必须带ON *.*,写成ON mydb.*会报错 -
REVOKE GRANT OPTION ON *.* FROM 'reporter'@'10.20.%'—— 收回后,该用户不能再把任何权限转授他人 - 如果用户通过角色获得权限,得对角色
REVOKE,再SET DEFAULT ROLE刷新
真正难防的不是语法层面的 DROP,而是权限链路里的隐式授权和临时提权。权限最小化不是一次配置,而是每次变更都要重审——尤其当 DBA 账号被用于自动化脚本时,很容易成为漏洞入口。


















