问题出现得很突然,日志倒是留得挺全。支付回调的幂等表主键定义成 varchar(64),业务用 sha256(订单号+渠道) 生成键,长度正好 64 字符。后来加环境前缀,键变成 74 字符被 MySQL 截断,不同订单截断后撞在一起,回调被误判成重复直接返回成功,真实订单漏发了两笔。
刚开始盯着「截断后的键碰撞概率随数据量增长迅速上升」,折腾了半个多小时,排掉了。
排查方向其实有限,先把可能性排一排。列宽按旧键长设计,加前缀后超出被静默截断。另一种情况是严格模式没开,插入超长值只给一条 warning。
另外,幂等命中后直接返回成功,把漏单掩盖掉了。
先看列定义与索引长度
mysql -e "SHOW CREATE TABLE idem_key\G" pay
算一下新键的实际长度
python -c "import hashlib;print(len(hashlib.sha256(b'prod:order:1').hexdigest()))"
然后把列改成 128 并重建唯一索引
mysql -e "ALTER TABLE idem_key MODIFY k VARCHAR(128) NOT NULL" pay
等前面都确认过,再开严格模式验证超长会报错
mysql -e "SET SESSION sql_mode='STRICT_ALL_TABLES'; INSERT INTO idem_key(k) VALUES (REPEAT('a',129))" pay
改完先别撤,确认这几项。键长超出时插入直接报错而不是截断,回调重放后每个订单只有一条记录,漏单数量归零。
容易忽略的是,幂等键是最不该省长度的地方,加前缀前先算新键长,列宽留余量并打开严格模式,能避开最难查的那类漏单。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。
