触发器内无法实现有效重试,因其作为事务一部分无法脱离宿主事务控制重试;真正可行的是将重试逻辑移至存储过程或应用层,由其封装核心操作并管理重试策略。

触发器里直接写重试逻辑基本没用
SQL 触发器本身不支持循环、延迟或捕获死锁错误后重试——ROLLBACK 一发生,整个事务就终止了,触发器里的语句根本不会执行到“重试”那步。你写的 WHILE 或 TRY...CATCH(SQL Server)在触发器里要么报错,要么被事务回滚直接吞掉。
常见错误现象:Deadlock encountered and was deadlocked out of the lock hierarchy 或 MySQL 的 ERROR 1213 (40001): Deadlock found when trying to get lock,但触发器里加了 WAITFOR DELAY 或 SLEEP() 后,反而让死锁窗口更长、冲突更频繁。
- 触发器是事务的一部分,不能脱离宿主事务独立控制重试
- MySQL 不支持触发器内调用存储过程做重试(除非用
GET DIAGNOSTICS+ 外部协调,但不可靠) - PostgreSQL 的
AFTER触发器无法修改当前事务状态;BEFORE触发器也不能阻塞并重试 - 真正能做重试的,只能是应用层或存储过程入口处
把重试移到存储过程里才可控
把原本放在触发器里的核心逻辑(比如更新统计表、同步日志)抽出来,封装成带重试的存储过程,再让触发器调用它——这看似绕路,实则是唯一能落地的方式。关键在于:触发器只负责「发起请求」,重试由存储过程自己管理。
使用场景:比如订单表 orders 的 AFTER INSERT 触发器,需要同步更新 user_stats 表。别在触发器里直接 UPDATE user_stats,而是调用 sp_update_user_stats_with_retry(@user_id)。
- SQL Server 示例中,用
@@ERROR = 1205判断死锁,配合WAITFOR DELAY '00:00:00.1'和最多 3 次循环 - MySQL 需用
GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE捕获40001,再手动REPEAT ... UNTIL - 每次重试前必须
SAVE TRANSACTION(SQL Server)或确保在独立事务块内(MySQL 用START TRANSACTION) - 注意嵌套事务问题:触发器内启动新事务,可能和外层事务冲突,建议设
SET XACT_ABORT ON或显式禁用自动提交
应用层重试比数据库层更可靠
如果业务允许,把重试逻辑彻底移出数据库,交给应用代码处理,是最灵活也最易调试的方式。数据库只做确定性操作,不确定性(如重试、退避、熔断)交给 Go/Python/Java 控制。
参数差异明显:数据库重试通常固定次数+固定延迟,而应用层可用指数退避(100ms → 300ms → 900ms)、可监控重试分布、可记录失败上下文。
- Go 中用
backoff.Retry库包装db.Exec调用,捕获sql.ErrTxDone或具体驱动错误码(如 pgx 的pgconn.PgError.SQLState() == "40001") - Python 的
tenacity可精准匹配psycopg2.errors.DeadlockDetected异常 - 避免在 ORM 层(如 Django 的
save()或 SQLAlchemy 的session.commit())外裸写重试——必须包裹整个事务生命周期 - 注意连接池行为:重试时若复用已标记为
bad的连接,会掩盖真实死锁,应确保重试走新连接或主动rollback()后释放
哪些操作千万别放进触发器重试
有些操作一旦失败,重试不仅无效,还会放大问题。这些不是“能不能”,而是“根本不该”出现在触发器重试链路里。
- 调用外部 HTTP API:网络超时不可控,重试可能造成重复通知,且触发器无法感知响应体
- 写入另一张正在被高频更新的表(如实时计数器):重试只会加剧锁竞争,不如改用异步队列
- 涉及
SELECT ... FOR UPDATE的复杂查询:死锁概率随重试次数非线性上升 - 修改触发器自身所在的表(递归触发):MySQL 会直接报错
Can't update table 't' in stored function/trigger,SQL Server 依赖SET TRIGGER_NESTLEVEL限制,但重试会让嵌套更难追踪
死锁重试真正的复杂点不在语法怎么写,而在于判断「这个操作到底值不值得重试」——很多你以为要重试的,其实该砍掉、拆开、或者扔进消息队列。留个心眼:只要触发器里出现 WHILE、SLEEP 或嵌套事务,先停一下,问问自己是不是在用数据库扛本该由架构分担的压力。

















