| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 |
- -- ============================================================
- -- 支付回调幂等加固:t_pay_log.out_trade_no 唯一索引
- --
- -- 事故背景:2026-10-01 21:06 商户订单号 202610012106065931549256(30 元)
- -- 微信支付回调被 Redis 幂等锁误判为"重复通知",代码在未做任何入账的情况下
- -- 应答了 HTTP 200,微信认为处理成功即停止重试,用户充值永久丢失。
- -- 修复后:Redis 锁仅用于削峰,幂等的最终保证是本唯一索引
- -- (并发投递时只有一个事务能落库,落库失败的一方整体回滚,不会重复加钱)。
- -- ============================================================
- -- 【执行前检查 1】是否已存在重复的商户订单号(正常应为 0 行;有结果必须先人工核对处理)
- -- 若本查询有结果(当前生产库为 10 条),请先执行 database/8-dedupe-pay-log.sql
- -- 完成"诊断 → 定损 → 备份 → 去重",返回 0 行后再继续本文件。
- SELECT out_trade_no, COUNT(*) AS cnt, MIN(id) AS keep_id, GROUP_CONCAT(id) AS ids
- FROM charge_app.t_pay_log
- GROUP BY out_trade_no
- HAVING COUNT(*) > 1;
- -- 【执行前检查 2】当前索引情况(若已存在 uk_pay_log_out_trade_no 则无需重复执行变更)
- SELECT INDEX_NAME, NON_UNIQUE, COLUMN_NAME
- FROM information_schema.STATISTICS
- WHERE TABLE_SCHEMA = 'charge_app'
- AND TABLE_NAME = 't_pay_log';
- -- 【正式变更】商户订单号唯一(微信侧 out_trade_no 本身唯一)
- ALTER TABLE charge_app.t_pay_log
- ADD UNIQUE KEY uk_pay_log_out_trade_no (out_trade_no);
- -- 【验证】NON_UNIQUE 应为 0
- SELECT INDEX_NAME, NON_UNIQUE, COLUMN_NAME
- FROM information_schema.STATISTICS
- WHERE TABLE_SCHEMA = 'charge_app'
- AND TABLE_NAME = 't_pay_log'
- AND INDEX_NAME = 'uk_pay_log_out_trade_no';
- -- 【上线后对账 1】唯一性:应返回 0 行
- SELECT out_trade_no, COUNT(*) AS cnt
- FROM charge_app.t_pay_log
- GROUP BY out_trade_no
- HAVING COUNT(*) > 1;
- -- 【上线后对账 2】正向:已确认的充值流水必须都能在 t_pay_log 找到同一订单号,应返回 0 行
- -- 有结果 = 该笔充值缺少支付记录(可能被清理误删,或历史人工补单),需逐条确认后补齐 pay_log,
- -- 否则后续再收到该订单回调会走不到幂等快路径而重复入账
- SELECT w.id, w.user_id, w.order_no, w.amount, w.status, w.create_time
- FROM charge_app.t_wallet_detail w
- LEFT JOIN charge_app.t_pay_log p ON p.out_trade_no = w.order_no
- WHERE w.type = 1 AND w.status = 1 AND p.id IS NULL;
- -- 【上线后对账 3】反向:成功的支付记录必须都能找到对应的已确认充值流水,应返回 0 行
- -- 有结果 = 微信收了钱但钱包流水没确认(本次事故就是这一类),需按 8-incident-*.sql 的方式补单
- SELECT p.id, p.user_id, p.out_trade_no, p.total, p.transaction_id, p.success_time
- FROM charge_app.t_pay_log p
- LEFT JOIN charge_app.t_wallet_detail w
- ON w.order_no = p.out_trade_no AND w.type = 1 AND w.status = 1
- WHERE p.trade_state = 'SUCCESS' AND w.id IS NULL;
|