应监听订单表中表示支付成功的字段变更,如status从非paid变为paid,或pay_time从null变为非null,仅响应单向跃迁,避免误触发。

触发器该监听哪张表、哪个字段的变更
分销返利的核心动作是「订单支付成功后,按规则给上级代理打款」,所以必须监听订单表中表示「已支付」状态的字段。常见错误是监听 status 字符串字段(如 'paid'、'shipped'),但没限定旧值 → 新值的跃迁路径,导致发货、退款等操作也误触发。
推荐做法是只响应从非支付态到支付态的单向变更:
- MySQL/PostgreSQL 触发器中用
OLD.status != 'paid' AND NEW.status = 'paid'判断 - 避免监听
updated_at或全量UPDATE,否则后续运营改备注也会重复计算 - 如果订单表无原子状态字段,而是靠
pay_time IS NOT NULL判断,那就直接检查OLD.pay_time IS NULL AND NEW.pay_time IS NOT NULL
返利逻辑不能写在触发器里
触发器内执行复杂计算(比如查多级代理、递归算比例、调外部API)会严重拖慢主事务,甚至锁表。MySQL 触发器还不支持事务内提交,一旦返利表写入失败,整个订单更新就回滚——这违背「订单必须成功」的业务底线。
正确分层方式是:
- 触发器只做最轻量的事:往
pending_commissions表插入一条记录,含order_id、amount、created_at - 由独立的定时任务(如每分钟跑一次的 Python 脚本)或消息队列消费者拉取
pending_commissions记录,执行查代理链、校验冻结资金、写分账流水等重逻辑 - 若需强一致性,可用数据库通知(如 PostgreSQL 的
NOTIFY)代替轮询,但别在触发器里INSERT同时又SELECT ... FOR UPDATE
多级代理的层级遍历必须防环和限深
代理关系常存于 users 表的 parent_id 字段,直接用自关联递归查询(如 MySQL 8.0+ CTE)风险很高:一旦数据异常形成环(A→B→C→A),查询会死循环或超时;若代理链过长(>10级),可能拖垮数据库。
实操建议:
- 在代理关系表加
level字段(注册时由应用层计算并维护),查询时用WHERE level BETWEEN 1 AND 5硬限制 - 若必须动态查,PostgreSQL 可用
WITH RECURSIVE ... LIMIT 10,MySQL 需在应用层用循环 +MAX_DEPTH=5控制 - 返利前先查
SELECT COUNT(*) FROM users WHERE id IN (路径ID列表) AND status != 'active',跳过已停用代理,避免脏数据干扰
触发器里不能依赖当前时间函数做业务判断
像 NOW()、CURRENT_TIMESTAMP 在触发器执行时返回的是语句开始时间,不是行变更的精确时刻。更麻烦的是,如果订单批量更新(如运营后台补单),所有触发器看到的 NOW() 是同一个值,会导致返利时间戳全部相同,后续对账困难。
安全做法是:
- 订单表显式存
paid_at字段,由应用层在支付回调时赋值(哪怕只是UTC_TIMESTAMP()),触发器只读这个字段 - 返利记录表(
commissions)的triggered_at字段,不要用触发器里的NOW(),而是在下游处理任务中用程序当前时间写入 - 绝对不要在触发器里做「距支付时间是否超24小时」这类判断——时间窗口类逻辑必须移出触发器
真正难的不是写触发器,而是让返利结果可追溯、可对账、可逆向修正。状态监听只是起点,后面每一层异步处理都要带幂等键、失败重试和人工干预入口。










