What

具体场景:工单表(Ticket)存在接单人、状态、客户信息等字段,但存在转单(即接单人更改)后,转单记录表存在记录(TransferRecord),但实际转单人未更改成功的情况。

Bug表现:出现表数据部分字段未更改成功?(偶发,转单发生率 ≈ 1/1000)

可否复现:测试环境未能复现 😂

Why

诡异点:转单API的日志打印,工单表(Ticket)接单人字段的更新真真实实成功了,而且也不存在啥并发

(⊙o⊙)… ,莫非是哪个 👻 程序改了这个表?

思路有了,该业务存在定时服务,走查代码,初看并没有对 Order 表接单人字段人的更改 … …

甩锅失败,但个人感觉还是这个定时服务出了问题,继续细看,发现一段 Ticket 表的保存,其业务逻辑(每隔 10s 定时)如下:

  1. 获取创建时间 10 分钟内且未处理过的 Ticket (company_id =0 表示未置过该值)
  2. 调用某个(其他系统的) API,获取客户信息,并根据返回的 res 保存对应 Ticket 表里面的客户信息(其他系统的记录ID & 客户等级)
  3. 保存 Ticket 表

这段业务背景:调用的某个API为旁支业务(线上响应较慢且有可能失败,需要重复调用 …),创建工单( Order)时调用恐影响主要逻辑


真相只有一个 👇 (Golang Gin 实现,看不懂没关系,仅看执行逻辑即可)

哪一步做了 Save?🙌 DB.Save(&t)

这边的 t 指针数据为调用 api.GetCompanyInfoByEmail 之前的 t 整个 Ticket 单的数据!!!

所以,如果API调用及处理(时间差)期间,发生了转单,也就是接单人 t.ToEmail 变了(API 服务调用),但是这边的 t.ToEmail 还是老数据的覆盖,就产生了类似「字段未更改成功」的现象。

该 Bug 偶发的原因:

  • ”其他系统 API“ 调用时长的时间差是否与转单发生时间交错(测试环境该 API 很快,所以未复现)
  • 实际业务创建 10s 内的情况下也不常发生转单 🙃

How

问题提定位后,解决就不难了。确定 CompanyID 以及 VipLevel 只在该定时业务处理后,该定时业务代码 Fix 为只更新这两个字段(+更新时间 🙃)即可。

Bug Fixed。但是,有啥值得反思的呢?

  1. 核心问题:业务逻辑未理清,旁支处理更新了全表 =》应处理为只更新需要更新的字段
  2. 异步处理放在定时服务处理,有点奇怪但也不失为一种方式,但会造成排错成本的增加。如果把该异步处理放在转单API 里面,加个协程处理更佳(即使协程处理也须注意只更新需要的字段)。