提到mysql黑名单,大多数人第一反应是:能不能像防火墙一样,把某些SQL、某个账号、某个来源IP直接拉黑,让它在数据库上彻底失效。我常年干一线MySQL运维,被问过最多的需求之一就是这个。说实话,MySQL内核本身并没有给你一个叫blacklist的开关,这也是很多人翻遍官方文档都找不到标准答案的原因。
前阵子就有个客户,开发同学在测试环境执行UPDATE时少写了一个WHERE,几万行关键字段瞬间被更新成同一个值,流程走到第5个库才发现异常,最后只能靠备份回滚。当时立项想搞一套"黑名单防护体系",我把那段时间从方案选型到上线踩坑的过程完整整理了一遍,包括用中间件做SQL指纹拦截、用pt-kill做定时巡检、用账号权限做访问控制、用审计事件做自动发现,这些思路在MySQL 5.7和8.0上都能落地。文章偏工程实操,能直接抄作业,也适合打算给数据库上保险的人参考。
1. 先厘清概念:MySQL原生没有"SQL黑名单",大家口中的黑名单到底指什么
1.1 三类"黑名单"需求,对应三条完全不同的技术路线
"黑名单"这个词在MySQL运维语境里其实被说烂了,但它从来不是一个单一功能。我在客户现场聊需求时,不同人嘴里的"黑名单"往往是完全不同的东西,大致可以分成三类:
| 黑名单类型 | 典型场景 | 落地手段 |
|---|---|---|
| 语句级黑名单 | 全表UPDATE/DELETE、DROP TABLE、烂SQL把CPU打满 | ProxySQL/MaxScale规则、pt-kill巡检、事件调度器 |
| 来源/账号级黑名单 | 某个IP疯狂连接、离职账号还在连接池里 | 防火墙、账号锁定、主机白名单、connection_control插件 |
| 行为级黑名单 | 不允许锁表、不允许跨库JOIN、不允许大事务 | 权限最小化、只读实例、资源组限制 |
这三类需求的技术路线差异非常大。语句级黑名单讲究匹配效率和实时性;账号级黑名单讲究授权粒度和应急速度;行为级黑名单则往往要结合业务规范和架构改造,不是简单一条规则能搞定的。所以向别人说要"给MySQL加黑名单"之前,最好先明确是哪一种,否则方案选型会走偏。
1.2 MySQL内核为什么不做"blacklist开关"
MySQL不直接在解析器里提供黑名单机制,很多人不理解,甚至觉得这是设计缺陷。其实站在数据库内核的角度,原因很现实:SQL语法太灵活,同一句危险操作可以有无数种写法,比如DELETE FROM t、DELETE t FROM t、DELETE /*注释*/ FROM t,在解析层做规则匹配非常容易误伤;而且生产环境的SQL量大、并发高,每条语句都过一遍黑名单正则,性能和稳定性都会受影响。
更重要的是,MySQL的内核设计哲学是把"拦截"这件事交给上层组件。权限系统已经承担了正名单职责:你没被GRANT DELETE权限,就算写了DELETE也执行不了。至于更灵活的规则匹配,官方更希望你通过ProxySQL这类中间件、登录审计、账号管控来完成。MySQL官方其实也有几个弱约束,比如max_execution_time提示可以限制单条SELECT的执行时间(对UPDATE/DELETE不生效)、sql_mode的严格模式可以拒绝非法的数据写入,但这些都算不上"黑名单",只是很基础的防线。
理解这一点之后,你就知道接下来该往哪个方向使劲了:要么在中间层拦截,要么在账号权限层预防,要么靠巡检工具事后处置。三种方式组合使用,才能做出一个接近黑名单效果的完整方案。
1.3 黑名单上线前,先回答自己三个问题
我每次给客户设计黑名单方案,都会先让他们回答三个问题,答不清楚就不动手:
第一,拦截粒度选在哪一层?是按用户、按客户端IP、按库名,还是按SQL文本、按SQL指纹?粒度的选择直接决定规则数量和维护成本,SQL文本规则很好写,但误杀率也最高。
第二,命中后怎么处理?是只记录日志、返回错误信息、杀语句,还是直接断开连接?从保业务的角度看,一定要有个递增梯度:先记录、再告警、最后拦截,千万别一步到位。
第三,谁有权限改规则?黑名单规则越容易被修改,就越容易被绕过;但规则越难改,误杀时业务恢复就越慢。所以规则变更最好走一个简单的审批流,至少要在工单系统里留痕。
这三个问题想清楚,后面选型就是水到渠成的事。
2. 方案一:ProxySQL查询规则,一行error_msg就能"拉黑"危险SQL
2.1 为什么首选ProxySQL
如果目标很明确,就是要拦截某几条危险SQL,我会首选ProxySQL。它在应用和MySQL之间充当反向代理,能解析前端发来的SQL,再按规则决定往后端哪个库转发,或者直接返回错误。线上做读写分离的团队往往已经部署了ProxySQL,加几条黑名单规则几乎零额外成本。
ProxySQL还有两个亮点非常适合黑名单场景:一是规则支持热加载,LOAD MYSQL QUERY RULES TO RUNTIME之后秒级生效,不需要重启MySQL,也不用重启应用;二是它能按SQL指纹(digest)匹配,而不是只匹配原始文本。SQL指纹会把语句中的字面值替换成占位符,比如DELETE FROM t WHERE id = 1和DELETE FROM t WHERE id = 2会归并为同一个指纹DELETE FROM t WHERE id = ?,这样即使参数千变万化,也能精确识别出同一类操作。
2.2 通过SQL指纹确定拦截对象
把这套方案跑起来需要三步。第一步是连上ProxySQL的管理端口,先看看到底哪些语句在消耗资源、哪些语句容易被误操作:
SELECT digest, digest_text, count_star, sum_time FROM stats_mysql_query_digest ORDER BY sum_time DESC LIMIT 20;管理端口默认是6032,用户名admin,初始密码admin。这条查询能列出ProxySQL观察到的高频SQL和总耗时排名,digest_text就是SQL指纹。比如你发现某个业务账号频繁执行DELETE FROM huge_table,而这张表压根不需要做删除操作,那它就是天然的黑名单对象。
第二步,把指纹写进规则库。注意规则是按rule_id从小到大匹配的,命中了就停止继续匹配,所以精确的规则一定要放在前面。假设我们要禁止对huge_table的全表删除:
INSERT INTO mysql_query_rules (rule_id, active, match_digest, error_msg, apply) VALUES (10, 1, '^DELETE FROM huge_table$', 'DELETE on huge_table is blacklisted', 1);关键字段是match_digest,这里填的是正则表达式,$代表SQL指纹必须到huge_table这里就结束,不会误伤huge_table_2025。error_msg是命中后返回给客户端的报错文本,客户端会收到一个语法错误或访问被拒的提示,这样开发同学立刻能反应过来。
第三步,让规则生效并持久化:
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;LOAD是把内存中的规则加载到运行状态,SAVE是写到磁盘,防止ProxySQL重启后规则丢失。这两条命令每次改完规则都得执行。
这里有一个重要前提:应用必须连接到ProxySQL的对外端口6033,而不是直连后端的3306。如果应用直连数据库,规则根本不会经过ProxySQL,自然拦截不到。很多团队在测试环境配完规则发现没效果,十有八九是这个问题。
2.3 命中后的处理方式与三种常见坑
ProxySQL的规则命中后,默认动作是返回error_msg里的错误信息,这对业务最友好。如果你想对某些极端危险的操作更狠一点,还能配合drop_connection=1断开客户端连接,但我强烈不建议把这个作为默认动作,因为它会让应用端的连接池集体报错,甚至引发雪崩。更稳的做法是:先用error_msg方式上线观察,确认不会误杀后再决定要不要加强制裁。
实际使用中有三个坑必须提前避。
第一个坑是连接池问题。ProxySQL规则的生效粒度是新到达的查询,如果应用连接池里的连接已经建立并处于空闲状态,规则变更后这些连接上发来的新查询仍然会被匹配,但那些已经发送到后端MySQL的语句无法被撤回。所以变更规则前,最好让应用侧做一次连接重连,或者在低峰期操作。
第二个坑是正则写得太宽。我见过有人为了省事,写了一条match_digest = 'DELETE FROM user',本意是禁止删除user表,结果把DELETE FROM user_log、DELETE FROM user_address全拦了。正确写法是加上边界字符,比如^DELETE FROM user[[:space:]]*$,至少要把匹配范围钉死在表名结尾。
第三个坑是SQL写法的多样性。正则只能匹配一种固定写法,但开发同学写SQL的方式千差万别,比如DELETE u FROM user u WHERE ...这种多表删除语法,用^DELETE FROM user就匹配不到。所以,对于核心业务表的删除操作,我建议不要依赖正则黑名单,而是直接在账号权限层把DELETE权限收掉,这样无论SQL怎么变,没有权限就是执行不了。
3. 方案二:pt-kill巡检,把"黑名单"做成定时任务
3.1 pt-kill能匹配什么,不能匹配什么
ProxySQL适合做前置拦截,但有些场景下你根本没法插中间层,比如存量架构已经直连数据库很久了,一时半会改不动端口。这时候就需要第二道防线:pt-kill。
pt-kill是Percona Toolkit里的工具,它的工作方式是定期扫描MySQL的processlist,把符合匹配条件的会话或语句杀掉。它的匹配维度包括:用户名(--match-user)、数据库名(--match-db)、命令类型(--match-command)、客户端IP(--match-host)、CPU/执行时间(--busy-time)、SQL文本内容(--match-info)。
有一点要明确:pt-kill本质上是个事后处理工具,它看到的SQL可能已经跑了很久,甚至已经出了问题。所以它的定位不是"阻止你执行危险SQL",而是"发现危险SQL正在执行时赶紧掐掉",防止数据库被拖垮。这也是为什么我建议把它和ProxySQL结合使用:中间件负责拦新增流量,pt-kill负责兜底存量连接。
3.2 两条实用命令:先观察、再执行
假设业务库里经常出现长时间运行的UPDATE和DELETE,我们想把这它们作为黑名单对象。第一条命令先用打印模式观察,不真正杀任何会话:
pt-kill --host=127.0.0.1 --port=3306 --user=monitor --password=xxx \ --match-command=Query \ --match-info='(?i)^(update|delete)[[:space:]].*' \ --busy-time=60 \ --print \ --log=/tmp/pt-kill-print.log这里有个很关键的设计:--busy-time=60意味着只匹配已经执行超过60秒的语句,而不是一看到UPDATE就杀。为什么?因为线上的删除和更新太常见了,如果都杀会误伤一大片。先加上超时阈值,把"危险操作"收敛为"危险且长时间运行的操作",误杀率会低很多。
观察一段时间后,如果日志里命中的确实都是该杀的SQL,就把--print改成--kill,真正执行:
pt-kill --host=127.0.0.1 --port=3306 --user=monitor --password=xxx \ --match-command=Query \ --match-info='(?i)^(update|delete)[[:space:]].*' \ --busy-time=60 \ --kill \ --log=/var/log/pt-kill.log注意权限问题。在MySQL 8.0里,要看到其他会话的完整SQL文本,监控账号需要PROCESS权限;要执行杀操作,还需要CONNECTION_ADMIN或SUPER权限。我在生产环境习惯单独建一个专用账号,只给它这两个权限,不要直接用root跑巡检任务。
3.3 定时任务的完整落地配置
pt-kill本身是个一次性命令,但黑名单防护需要持续运行,所以一般挂到cron里。我常用的调度是每5分钟执行一次,因为对于真正拖垮数据库的烂SQL,5分钟已经足够久了:
*/5 * * * * /usr/bin/pt-kill --host=127.0.0.1 --port=3306 --user=monitor --password=xxx --match-command=Query --match-info='(?i)^(update|delete)[[:space:]].*' --busy-time=60 --kill --log=/var/log/pt-kill.log 2>&1如果怕误杀,还有更保守的写法:先只杀某个"黑名单账号"的语句,不杀其他人的。比如历史上某账号经常写出全表更新,那就先针对它:
pt-kill --host=127.0.0.1 --port=3306 --user=monitor --password=xxx \ --match-user='bad_app' \ --match-command=Query \ --busy-time=30 \ --kill这个写法的逻辑是"用户黑名单 + 执行时间阈值"双重条件,命中范围小、可控性强。我比较推荐先从这个粒度开始跑,等确认无误再放宽到按SQL文本匹配。
另外,pt-kill日志会增长得很快,尤其是高并发库。最好给日志目录配logrotate,或者定期清空,否则日志盘会被打满,反而引发新故障。
4. 方案三:账号层"用户黑名单"——权限、主机与锁定的组合拳
4.1 查清家底:MySQL到底开了哪些账号
很多所谓的"黑名单需求",本质是账号权限失控。应用连库账号给了ALL PRIVILEGES,测试账号和生产共用一个库,离职同事的连接池还挂在上面。这种环境下,再怎么配SQL黑名单都是白费力气,因为用户自己就能给自己授权。
对付这种情况,第一步永远是盘点。在8.0里可以这样查询所有账号的状态:
SELECT user, host, plugin, account_locked, password_expired, password_last_changed FROM mysql.user WHERE user NOT IN ('mysql.infoschema', 'mysql.session', 'mysql.sys');重点关注三类账号:一是host为'%'的账号,这类账号可以从任意IP登录,风险面最大;二是plugin是老的mysql_native_password且密码过弱的账号;三是长期闲置、password_last_changed时间久远的僵尸账号。
盘完之后该删就删,该锁就锁。应急拉黑某个账号只需一条SQL:
ALTER USER 'legacy'@'%' ACCOUNT LOCK;锁定动作立即生效,新连接直接拒绝,已经建立的连接不受影响。注意先确认这个账号没有挂在调度任务或应用连接池里,否则业务方会收到一堆无法连接的告警。
4.2 主机白名单与来源控制
账号级黑名单中,来源IP控制是最容易被忽略的一环。MySQL授权表里支持主机名匹配,但很多人建账号时图省事,一律用'%'通配。这等于把门钥匙复制给了所有可能路过的人。
正确的做法是建号时就限定网段:
CREATE USER 'app'@'10.0.0.%' IDENTIFIED BY '强密码'; GRANT SELECT, INSERT, UPDATE, DELETE ON bizdb.* TO 'app'@'10.0.0.%';对于已经存在的老账号,虽然可以用RENAME USER把它从'%'改到具体网段,但要注意这个过程会同时影响所有来源的连接,而且改错的代价很高。所以我的实操习惯是:新账号一律白名单IP段,老账号只锁不用先改,等高危操作发生时再通过防火墙快速封禁来源IP。
真正遇到某个IP疯狂打库时,最快的应急手段其实不在MySQL里,而是在防火墙层直接DROP掉该IP,因为MySQL层的权限变更也好、账号锁定也好,都要等新连接建立时才生效,而防火墙一击就能阻断。
4.3 密码过期与登录失败惩罚
账号黑名单还有一层是"防暴力破解"。MySQL官方提供了connection_control插件,可以给连续登录失败的来源IP增加延迟,相当于登录层面的黑名单。
启用方法如下:
INSTALL PLUGIN CONNECTION_CONTROL SONAME 'connection_control.so'; INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME 'connection_control.so'; SET GLOBAL connection_control_failed_connections_threshold = 3; SET GLOBAL connection_control_min_connection_delay = 1000; SET GLOBAL connection_control_max_connection_delay = 60000;配置含义是:同一个来源连续失败3次后,之后的每次连接尝试都会被强制延迟,最少1秒,最长1分钟。这个延迟是逐渐递增的,对正常用户影响很小,但能让爆破工具的尝试频率断崖式下跌。
密码过期策略也建议顺手打开,尤其是对于使用频率低的管理账号:
ALTER USER 'dba'@'10.0.0.%' PASSWORD EXPIRE INTERVAL 30 DAY;密码过期的账号登录后处于受限模式,只能改密码,做不了其他任何操作,这在安全性要求高的环境里很实用。不过这个策略最好先在测试账号上验证,因为总有人忘记改密码然后被锁在门外。
4.4 权限最小化才是终极"黑名单"
说到底,黑名单再全也是被动的,真正的釜底抽薪是权限最小化。如果一个应用账号压根没有DELETE权限,那全表删除这一类的问题就不存在,也不需要花心思写正则去匹配。
实操时我会做一个动作:给账号分类。应用账号只给DML权限(SELECT、INSERT、UPDATE、DELETE),不给DDL权限;报表账号只给SELECT;运维账号才给DDL,而且按库隔离。对于核心订单表这类"删了会出大事"的表,干脆从应用授权里拿掉DELETE权限:
REVOKE DELETE, DROP, ALTER ON prod.* FROM 'app'@'10.0.0.%';如果业务上确实需要删除部分数据,应该走临时授权流程,DBA审批后限时开放。这套路虽然增加了流程成本,但它才是告别"删库跑路"事故的根本保障。
5. 方案四:审计事件配合定时任务,让黑名单规则自动运营
5.1 数据来源:抓取"该进黑名单"的语句
人工维护黑名单规则总有滞后性,真正专业的做法是建立一套"自动发现黑名单对象"的机制。MySQL的performance_schema里有一张很有用的表:events_statements_summary_by_digest,它会按SQL指纹累积统计每条语句的执行次数、总耗时、影响行数。
用它可以找出"哪些语句最值得进黑名单":
SELECT schema_name, digest, digest_text, count_star, ROUND(sum_timer_wait / 1e12, 2) AS sum_time_sec FROM performance_schema.events_statements_summary_by_digest WHERE digest_text LIKE '%DELETE%' OR digest_text LIKE '%UPDATE%' ORDER BY sum_time_sec DESC LIMIT 20;这张表是累计统计,所以查看前最好先做一次截断,方便观察一个固定周期内的数据:
TRUNCATE TABLE performance_schema.events_statements_summary_by_digest;注意,这张表的截断操作在高版本MySQL里行为略有差异,建议先在测试实例上验证一次。除了性能摘要表,还有information_schema.processlist可以实时抓当前正在执行的SQL,这是黑名单巡检的数据源之一。
5.2 存储过程:命中规则、记录、选择性KILL
有了数据源,下一步就是自动化。我常用的做法是写一个存储过程,定期扫描processlist,把命中的SQL记录到日志表,并视情况执行KILL QUERY。先建日志表:
CREATE DATABASE IF NOT EXISTS ops; USE ops; CREATE TABLE IF NOT EXISTS blacklist_log ( id INT PRIMARY KEY AUTO_INCREMENT, ts DATETIME DEFAULT CURRENT_TIMESTAMP, user_host VARCHAR(100), db VARCHAR(64), sql_text VARCHAR(1024), action VARCHAR(32) DEFAULT 'LOG' ) ENGINE=InnoDB;然后写巡检存储过程。下面的例子用于发现"看起来像全表DELETE且执行时间超过5秒"的语句,记录并杀语句:
DELIMITER $$ CREATE PROCEDURE sp_blacklist_monitor() BEGIN DECLARE v_id INT; DECLARE v_user VARCHAR(100); DECLARE v_db VARCHAR(64); DECLARE v_time INT; DECLARE v_info TEXT; DECLARE done INT DEFAULT 0; DECLARE cur CURSOR FOR SELECT id, user, db, time, info FROM information_schema.processlist WHERE command = 'Query' AND info IS NOT NULL AND info REGEXP '^DELETE[[:space:]]+FROM[[:space:]]+[a-zA-Z0-9_]+[[:space:]]*$'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur; read_loop: LOOP FETCH cur INTO v_id, v_user, v_db, v_time, v_info; IF done THEN LEAVE read_loop; END IF; INSERT INTO blacklist_log(user_host, db, sql_text) VALUES (v_user, v_db, v_info); IF v_time > 5 THEN SET @kill_sql = CONCAT('KILL QUERY ', v_id); PREPARE stmt FROM @kill_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END LOOP; CLOSE cur; END$$ DELIMITER ;这里面有个细节:KILL QUERY而不是KILL CONNECTION,前者只终止当前正在执行的语句,不断开连接,这样应用连接池不会集体报错,业务体验好很多。
如果你担心正则太激进,可以先只保留INSERT记录,把KILL QUERY那一段注释掉,跑几天看命中率。自动KILL和杀人的区别不在于技术,而在于你敢不敢承担误杀的后果。评估清楚之前,永远只LOG不KILL。
5.3 定时调度:从人工拉黑到自动运营
存储过程写好后,用事件调度器定时执行。先确认开关状态:
SET GLOBAL event_scheduler = ON;然后创建事件,比如每2分钟跑一次:
CREATE EVENT ev_blacklist_monitor ON SCHEDULE EVERY 2 MINUTE DO CALL sp_blacklist_monitor();这套机制跑起来后,黑名单规则就不依赖人了。performance_schema负责发现问题,存储过程负责处置,事件调度器负责周期执行,blacklist_log负责留痕追责。配合前面说的人工定期审查日志,就能形成"自动发现-自动处置-人工复核"的闭环。
这套方案的局限也很明显:它处理的都是已经发生的SQL,属于事后干预。但从实际效果看,只要巡检间隔足够短,完全可以在数据库被打死之前把坏SQL掐掉。我见过一个压测环境,靠着这个存储过程2分钟一轮巡检,硬是把一个原本能拖垮库的全表扫描任务在错误发生前拦截了大半。
6. 黑名单策略的真实运维经验:误杀、灰度与替代方案
6.1 一次误杀事故复盘:正则黑名单差点把业务打挂
黑名单规则有个天然风险:写的时候觉得万无一失,上线之后才发现覆盖了不该覆盖的SQL。我踩过最狠的一次坑,是在一个电商客户环境里配置ProxySQL,当时的本意是禁止对订单表的无条件删除,于是写了:
INSERT INTO mysql_query_rules (rule_id, active, match_digest, error_msg, apply) VALUES (10, 1, '^DELETE FROM order', 'operation is forbidden', 1);刚LOAD RUNTIME不到五分钟,线上就开始告警,下单流程直接报错。查了一圈才发现,退款逻辑里有一句DELETE FROM order_2024 WHERE ...,它用到了order前缀,结果也被拦了。更冤的是一些开发同学习惯用DELETE FROM order_detail WHERE ...,同样被黑名单误杀。
这次事故给我一个教训:正则匹配必须带边界。后来我把规则改成:
^DELETE[[:space:]]+FROM[[:space:]]+order[[:space:]]*$$锚定了表名的结尾,就不会再误伤其他名字以order开头的表。但即使这样,如果SQL写成DELETE o FROM order o WHERE ...这种多表语法,照样绕过了正则。所以对核心表的保护,不能只靠ProxySQL规则,必须配合权限控制,两条腿走路。
6.2 灰度策略:先记录、后拦截、再收紧
黑名单规则上线,最忌讳一步到位直接拦截。我的标准流程是分三个阶段走:
第一阶段,纯记录。无论ProxySQL、pt-kill还是存储过程,都先把命中结果写到日志,不执行任何拦截动作,观察至少一周。重点看两点:命中量是否正常、是否存在合法的业务SQL被标记为高危。
第二阶段,拦截高危中的高危。只对明确无争议的规则开启拦截,比如禁止对某张归档表执行DELETE、禁止某个黑名单账号执行任何写操作。这个阶段可以持续两周,收集业务方的反馈。
第三阶段,根据日志和反馈逐步收紧。比如把执行时间阈值从60秒降到30秒,把匹配范围从账号扩大到语句模式。每一次收紧都必须配套工单记录和回滚方案,一旦误杀能快速让规则失效。
ProxySQL里有rule_id的设计,新规则可以给最大的rule_id,这样排在最末尾,命中优先级最低,方便灰度。但注意ProxySQL按rule_id从小到大匹配,所以你想让灰度规则不生效,得靠active=0来临时禁用,而不是靠排序。
6.3 比黑名单更稳的几个"替代方案"
经验多了之后你会发现,黑名单规则永远是"针对已知问题的妥协方案",真正稳的架构是让高风险操作从根上没有发生的机会。
第一个替代方案是白名单权限,这比黑名单好维护得多。应用账号一开始就没授权DELETE、DROP、ALTER,想误删都没权限。如果业务确实需要删除,必须走临时授权审批,用完立刻回收。
第二个替代方案是只读实例。把报表查询、数据分析、甚至一些容易写错的批量操作全部引导到只读从库,主库的写操作风险面就小很多。很多客户说"我管不住开发写SQL",我只能说,管不住SQL就管住流量。
第三个替代方案是延迟从库。给核心库挂一个延迟1小时的从库,万一发生大范围误更新,可以从延迟从库上捞回数据。这不是黑名单,但比黑名单更能兜底,因为黑名单猜不到所有意外,而延迟从库可以应对意外本身。
第四个替代方案是审计。MySQL通用日志或者企业版审计插件,把每次危险操作记录在案,业务方自己看到审计日志比口头教育管用得多。处罚不是目的,但留痕真的能降低犯错概率。
我个人的习惯是,把黑名单当成"最后一道保险丝",而不是"唯一防线"。每次上线黑名单规则之前,我会先在测试环境里故意执行那些不该被拦的合法SQL,确认不会被误杀;再执行真正要拦的危险SQL,确认能拦得住。两侧都验证通过,才敢往生产环境推。这套流程虽然多花半天时间,但比起线上事故后的焦头烂额,性价比高太多了。