加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱制作网_池州站长网 (https://www.0566zz.com/)- 数据快递、应用安全、业务安全、智能内容、文字识别!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

写代码前没理清这三大逻辑?90%的Bug由此而生

发布时间:2026-11-06 11:30:18 所属栏目:语言 来源:DaWei
导读:  跟开发吵了一架。就上周三,他甩过来一个修复包说“查库慢的问题好了”,我盯着那条 update 语句看了半分钟,手还没点执行,人已经站起来绕过隔板冲他工位去了。他正啃包子,油星溅到键盘上,我指着 SQL 里那个 on conflict

  跟开发吵了一架。就上周三,他甩过来一个修复包说“查库慢的问题好了”,我盯着那条 update 语句看了半分钟,手还没点执行,人已经站起来绕过隔板冲他工位去了。他正啃包子,油星溅到键盘上,我指着 SQL 里那个 on conflict do update 的 set 子句问:“你理清触发条件了吗?”他嚼着说:“不就是冲突了就覆盖嘛。”我笑了一下,没接话,转身回座位,顺手把咖啡杯往左推了三厘米——杯子底下压着张纸,是十月那次上线后我手写的三行字,油笔写的,蹭花了。


  去年十月。我原以为只是个常规订单状态流转,结果灰度两小时,支付成功但订单卡在“预占”态。DBA 查到死锁时,运维老周正在机房换风扇,回来喘着气说:“主从延迟跳到三十秒,不是网络,是主库堵在一条 update 上。”我们三个蹲在开发小王工位后面看执行计划,小王手指悬在键盘上不敢敲回车,说“这逻辑不就俩分支吗”,我说:“你写的是俩分支,数据库跑出来是四个。”他愣住,我指着他那段 if-else 嵌套里漏掉的事务边界——第三层嵌套没加 begin,第二层 rollback 又没 catch 异常,业务代码里连日志都没打。窗外天黑了,灯自动亮起,屏幕上的行数一直在跳,我数了数,七百二十六行,全是同一张表的更新日志,但时间戳乱得像撒了芝麻。


  第一个没理清的,是状态跃迁的原子性。不是“能不能改”,是“改一半算不算已改”。比如用户取消订单,你把 status 从 1 改成 0,但库存释放那步失败了,这时 DB 回滚还是不回滚?小王说回滚啊,我说你看看事务隔离级别——他写的 read committed,可上游服务拿这个订单号调下游核销接口时,用的是另一个连接,它根本看不到未提交的变更,但它能看到刚刷进缓存的状态。结果呢?用户界面显示已取消,但库存还在扣,三天后财务对账差出俩单。我删了那段重试逻辑又补上,发现不是重试有问题,是重试之前的状态快照根本没固化。风扇一直在转,声音大得像台拖拉机,谁路过问了句“好了没”,我没抬头。


  第二个是并发控制里的“谁说了算”。你写了 SELECT FOR UPDATE,但锁的是哪几行?锁粒度是 id 还是 biz_code?更绝的是,你 select 的字段根本不在 where 条件里,还指望它能串起来做乐观锁?我们那个库存扣减接口上线前没压测,靠的是“理论上不会同时抢同一个商品”。结果首单高峰来了,十个线程对着同一条记录 update version=version+1,版本号被干到负数——MySQL 没报错,它默默给你存了个 -2147483648。领导来巡班时,看见我盯着监控屏上的 red 5xx 曲线,问我咋回事,我说:“有人觉得并发是玄学。”他点点头,没说话,转身去隔壁看报表系统了。


  第三个最要命:上下游契约的时效断层。上游说我通知你,下游说“收到”就算确认完成。结果那天我翻 binlog,发现支付回调成功发了三次,每次间隔五分钟,而下游系统每收到一次就自增一条流水号。它压根没校验幂等键,只看 HTTP 200 就 commit。等我们捞出日志比对,才发现第一次回调后它数据库挂了仨分钟,恢复时自己把 transaction 日志重放了一遍……所以三条流水都是真的,但业务上只有一笔钱。我手抖,删错了测试环境的备份集,导致复现周期延长了不少。后来运维老周教我怎么从 WAL 归档里硬扒数据,讲着讲着岔到值班安排上去了,说下月他排七天夜班,饭盒都换成保温三层的。我点头,外卖凉了,塑料盖子结了层雾。


文章配图,仅供参考

  真不是吹,90%真就这么来的。不是 SQL 写错,不是索引漏建,是动笔之前,没人把这三个逻辑当真事推演。你让开发画个状态图,他画完自己先质疑:“这图是不是太细了?”——操,你写代码都不怕细,怕什么图细?昨天跟产品对需求,她说“用户点了取消就立刻不可见”,我说“立刻是秒级?毫秒级?事务提交前可见?”,她眨眨眼说“反正不能让用户再点一次”。我就知道,完了,又是一个没厘清的断层。我原以为只要加个分布式锁就能兜住,结果线上跑了俩小时,才发现锁 key 是用 user_id+order_id 拼的,可前端传参里 order_id 是空字符串……


  我现在改了习惯。开发提 PR 前,得给我一份带时间戳的白板照:左边画状态跃迁箭头,中间标并发路径分叉点,右边写清楚每个接口的“已承诺”和“未承诺”字段。不写完,我不点测试按钮。至于那些写了一半又扔掉的方案?比如用 Redis Lua 做原子状态跳变,或者把库存扣减改成消息队列+最终一致性——我懒得写了。上个月饭堂改菜单,红烧肉没了,只剩咖喱鸡块,我吃了三顿,胃开始抗议。这事儿跟技术无关,但你知道的,有些火气,它得找个地方散出去。


  下周我打算把测试用例模板里第一行,改成“请注明此功能依赖的三个隐式契约”。要是填不出来……我就直接拒收。不是较真,是优胜劣汰。真这么简单,那今年 Q3 就不该有十五个 P0 bug 是因为“本应失败却成功返回”。

(编辑:我爱制作网_池州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!