不能直接通过SQL触发器向Redis写入刷新信号,因其同步执行且无网络能力,调用UDF或redis-cli会导致事务卡死、连接堆积、写失败;正确做法是触发器仅写轻量队列表,由外部服务异步轮询处理。

不能直接通过 SQL 触发器向 Redis 写入刷新信号——触发器里没有网络能力,也没有 Redis 客户端,任何试图用 redis-cli、sys_exec 或 UDF 调用 Redis 的做法,在生产环境都等于给数据库埋雷。
为什么触发器里调用 Redis 会卡死事务
MySQL 触发器是同步执行的,且运行在事务上下文中。一旦触发器内部尝试发起网络请求(哪怕封装成 UDF),就会立刻暴露三个致命问题:
-
ERROR 1422:部分 MySQL 版本禁止在触发器中做显式/隐式提交,而多数 Redis 客户端库(如 hiredis)底层会触发连接状态变更,间接违反该限制 - Redis 连接超时(默认几秒) → 整个
INSERT/UPDATE/DELETE卡住,客户端等待,连接堆积,最终触发max_connections溢出 - 网络抖动、Redis 临时不可用、DNS 解析失败 → 写操作直接报错回滚,业务侧看到的是“数据库写失败”,而非“缓存没刷成功”
真正可行的“触发器 + Redis”路径:只写队列表
触发器唯一安全职责是快速落盘一条轻量变更记录,把所有网络、序列化、重试逻辑交给外部服务。具体要这么做:
- 建中间表
cache_refresh_queue,字段至少含:id(自增)、table_name、row_id、op_type('INSERT'/'UPDATE'/'DELETE')、created_at、status(默认 'pending') - 在目标表上建
AFTER INSERT/UPDATE/DELETE触发器,仅执行INSERT INTO cache_refresh_queue (table_name, row_id, op_type) VALUES ('users', NEW.id, 'INSERT');(DELETE 用OLD.id) - 避免在触发器里拼 URL、查关联数据、JSON 序列化——这些全留给轮询服务处理,触发器执行时间必须控制在 1ms 内
外部轮询服务怎么设计才不丢数据
这个服务不是“定时查一下就完事”,它得扛住失败、幂等、并发和监控缺失这四类典型问题:
- 每次查询加
FOR UPDATE SKIP LOCKED(MySQL 8.0+)或用SELECT ... LIMIT 1+UPDATE ... WHERE id = ? AND status = 'pending'避免多实例重复消费 - 调用 Redis 前先
GET确认 key 是否已存在,再决定用SET还是HSET;对 UPDATE/DELETE,建议先DEL再写新值,防止旧值残留 - 失败后更新
status = 'failed'并记录error_code和retry_count,后续按指数退避重试(如 1s → 3s → 10s) - 必须设硬性超时:
redis.set()调用带timeout=1000(毫秒),避免单次失败拖垮整个批次
最常被忽略的一点:binlog 格式必须是 ROW,否则触发器写的队列表本身可能不被下游同步工具捕获——如果你后续想用 Canal 把队列表变更也接入监控或审计,这点直接决定链路是否可观察。


















