案例3:复杂 Bug 调试
场景:生产环境 API 随机返回 500 错误,无法稳定复现,日志信息不完整。
挑战:间歇性错误、日志不够、根因在复杂的并发逻辑里
第一步:提供所有可用信息
你(附上日志):
生产环境有个随机出现的 500 错误,大约每 100 次请求出现 2-3 次。
我贴一下错误日志:
[粘贴日志片段]
已知信息:
- 只在高并发时出现(超过 50 个并发请求)
- 只影响 /api/orders/create 端点
- 重启服务后短暂消失,但过一段时间又会出现
- 数据库没有报错
你能看出这是什么类型的问题吗?有什么思路?Claude(分析):
从日志模式分析,这很可能是竞态条件(Race Condition)。
证据:
1. "Database locked" 错误有时候出现(SQLite 的锁竞争)
2. 只在高并发出现
3. 重启后消失(说明是运行时状态问题,不是启动配置)
建议检查方向:
1. orders/create 路由里是否有非原子的"先读后写"操作
2. 是否有共享的内存状态(全局变量、模块级缓存)
3. 数据库事务是否正确隔离了并发写入
我需要看 orders/create 的路由代码和相关 service。第二步:深度代码分析
你:
好,相关代码在 src/routes/orders.ts 和 src/services/orderService.ts。
帮我深入分析:
1. 找出所有可能的竞态条件
2. 找出没有事务保护的数据库写入
3. 找出共享状态
分析完列出所有可疑点,从最可能到最不可能排序。Claude 分析后输出(摘要):
发现 3 个可疑点(按可能性排序):
1. [高] orderService.ts 第 89-103 行
先 SELECT 检查库存,再 UPDATE 扣减,没有事务
在高并发下,两个请求都可能读到"库存充足",然后都扣减
→ 典型的 TOCTOU 竞态条件
2. [中] orderService.ts 第 45 行
全局变量 `let pendingOrders = 0` 在并发请求中被读写
没有加锁,可能导致计数错误
3. [低] routes/orders.ts 第 23 行
requestId 用 Date.now() 生成,高并发时可能重复关键技巧:让 Claude 排序可疑点
不要让 Claude 一次修所有问题。先分析,排序,然后从最可能的开始修,每次修一个,验证后再继续。
第三步:先写能复现的测试
根据分析,问题最可能是库存扣减的竞态条件。
在修复之前,先写一个测试来复现这个 bug:
- 模拟 10 个并发请求同时下单
- 库存只有 5
- 预期结果:只有 5 个成功,5 个失败
- 实际结果(修复前):可能超过 5 个成功
用 Jest 实现这个并发测试。第四步:修复
你:
测试确实失败了,复现了 bug。
现在修复第一个问题:库存扣减的竞态条件。
方案:用数据库事务 + 行锁,把"检查库存"和"扣减库存"变成原子操作。
修改 orderService.ts,不要改其他文件。改完跑并发测试,验证测试通过。Claude 修复并跑测试,输出:
修复完成。代码改动:
[展示 diff]
测试结果:
✓ 并发 10 请求,库存 5 → 5 成功,5 失败 ✅
✓ 所有原有测试通过 ✅第五步:修复其他问题
第一个问题修了,继续修第二个:
pendingOrders 全局变量的并发问题。
用原子操作或者锁来保护它,或者考虑是否真的需要这个全局状态。
修完跑测试。第六步:部署验证计划
你:
两个问题都修了,测试全绿。
在部署前,帮我制定一个验证计划:
1. 应该监控哪些指标
2. 如果修复没有生效,怎么快速回滚
3. 建议增加什么日志,方便下次排查类似问题
另外,commit 信息里要说明:
- 问题根本原因
- 修复方案
- 如何验证调试思路总结
| 阶段 | 提示词技巧 |
|---|---|
| 收集信息 | 提供尽量多的上下文:日志、复现条件、已知信息 |
| 分析阶段 | 让 Claude 给「可能性排序」,而不是直接修所有问题 |
| 复现优先 | 先写能失败的测试,再修复,有了测试才能验证修复 |
| 逐一修复 | 一次只修一个问题,修完验证再继续 |
| 部署计划 | 修复后讨论监控和回滚策略,不要「假设没问题」 |
可复用的 Debug 提示词:
有个 bug,情况如下:
现象:[具体症状]
复现条件:[什么时候出现]
已知信息:[你知道的一切]
日志:
[粘贴相关日志]
请:
1. 分析可能的根本原因(按可能性排序)
2. 不要急着修,先分析
3. 分析完后,我来决定从哪个方向开始