必须在执行前识别删除意图而非事后检查,可通过mysql enterprise firewall、proxysql代理拦截或审计日志+外部脚本告警实现风险控制。

直接执行 DELETE 语句时如何捕获大删行为
MySQL 自身不提供“删除行数超阈值就报警”的内置机制,DELETE 语句执行完才返回影响行数,等你看到 Query OK, 123456 rows affected 时,数据已经没了。所以不能靠事后检查日志或影响行数来“拦截”,必须在执行前识别风险。
关键点在于:监控对象不是“删除结果”,而是“删除意图”——即 SQL 语句本身是否匹配高危模式。
- 典型危险模式包括:
DELETE FROM users WHERE 1=1、DELETE FROM order_log WHERE create_time (无 LIMIT)、<code>DELETE FROM t WHERE id IN (SELECT ...)(子查询未加限制) - 这类语句往往缺少
LIMIT,且 WHERE 条件宽松或缺失,容易误删全表或大量数据 - 仅靠慢查询日志(
slow_query_log)无法覆盖——大删可能执行很快,不进慢日志;通用查询日志(general_log)又太重,生产环境通常关闭
用 MySQL Enterprise Firewall 拦截高危 DELETE
如果你用的是 MySQL 8.0+ 商业版,mysql_firewall 插件能基于 SQL 模式做白名单/黑名单控制,是最接近“实时拦截”的方案。
实操步骤:
- 启用插件:
INSTALL PLUGIN mysql_firewall SONAME 'mysql_firewall.so'; - 开启学习模式:
SET GLOBAL mysql_firewall_mode = 'RECORDING';,让系统先记录正常业务的 DELETE 模式 - 切换到保护模式:
SET GLOBAL mysql_firewall_mode = 'PROTECTING'; - 添加黑名单规则(例如禁止无 LIMIT 的 DELETE):
CALL mysql_firewall.add_user_rule('app_user', 'DELETE FROM % WHERE %');—— 这会匹配所有带 WHERE 的 DELETE,但需配合更精细的正则或后续人工审核
⚠️ 注意:mysql_firewall 不支持正则中的行数判断(比如“WHERE 后面没跟 LIMIT”),它只做语法模板匹配,真正拦截依赖你预设的规则粒度。误报率高时,建议先用 RECORDING 模式观察一周再上线。
用代理层(如 ProxySQL 或 MaxScale)做 SQL 改写与拦截
开源方案里,ProxySQL 是最成熟的选择。它能在 SQL 到达 MySQL 前解析并干预,比数据库内核层更灵活。
配置要点:
- 在
mysql_query_rules表中插入规则,匹配 DELETE 语句:INSERT INTO mysql_query_rules (active,username,match_pattern,replace_pattern,apply) VALUES (1,'app_user','^DELETE\s+FROM\s+[^\s]+\s*(WHERE.*)?$','SELECT CONCAT(''BLOCKED: DELETE without LIMIT on '', REGEXP_SUBSTR(?, ''FROM\\s+([^\s]+)'')) AS _msg',1); - 关键逻辑是:只要
DELETE语句不含LIMIT关键字,就用replace_pattern把它替换成一条提示性 SELECT,阻止真实执行 - 对已有业务,可先设
apply=0记录日志不拦截,确认规则准确后再启用 - 注意:ProxySQL 默认不解析注释,如果业务代码里把
LIMIT写在注释里(如-- LIMIT 100),会被忽略,仍触发拦截
审计日志 + 外部脚本实现事后告警(最低成本方案)
如果既不用商业版,也不愿引入代理层,就只能退一步:接受“删完才知道”,但做到秒级告警+快速止损。
前提:开启 MySQL 审计日志(audit_log 插件)或启用 general_log(仅临时开启,且必须配 log_output = TABLE 避免 I/O 崩溃)。
- 查最近 30 秒内 DELETE 行数异常高的语句:
SELECT argument, rows_affected FROM mysql.general_log WHERE command_type = 'Query' AND argument LIKE 'DELETE%' AND event_time > NOW() - INTERVAL 30 SECOND ORDER BY rows_affected DESC LIMIT 5; - 用外部脚本(Python + pymysql)每 10 秒轮询一次,当
rows_affected > 1000时,立刻发钉钉/邮件,并自动执行KILL QUERY xxx(仅对还在执行的语句有效) - 真正的止损动作不是停 SQL,而是立刻
FLUSH TABLES WITH READ LOCK防止进一步变更,然后从最近备份 + binlog 回滚
这个方案最易落地,但本质是“亡羊补牢”。真正难的不是写告警脚本,而是确保 binlog 格式为 ROW 且保留足够久(至少 7 天),否则连回滚都做不到。











