关键在于“控影响”而非“防崩溃”,需通过分类异常处理实现带伤运行:业务异常跳过、临时异常重试、不可恢复异常抛出并告警;try块应窄而准,仅包裹风险动作;用结构化错误容器替代裸列表;兼顾性能与可观测性。

关键不在“防崩溃”,而在“控影响”——单条脏数据不该拖垮整个批量任务。循环体内做异常分类阻断,本质是让程序具备“带伤运行”的能力:识别问题、隔离问题、记录问题、继续干活。
明确三类异常,分而治之
不是所有异常都该被 catch,更不该用同一个 catch 块糊弄过去:
- 业务可预期异常(如:用户手机号格式错误、订单状态非法)——单独捕获,打标记录,跳过当前项,不中断循环
- 系统级临时异常(如:网络超时、数据库连接抖动)——捕获后加指数退避重试(最多2次),失败再降级或记错
- 不可恢复异常(如:空指针、数组越界、ClassCastException)——这类通常暴露代码缺陷,不应静默吞掉;记录完整上下文(含当前索引、原始数据、线程ID)后抛出包装后的业务异常,由外层统一熔断或告警
循环内 try-catch 的写法要“窄而准”
只包裹真正可能出问题的那几行,别把日志、状态更新、计数器也包进去:
- ❌ 错误示范:
try { log.info("处理第{}条", i); item.validate(); saveToDB(item); counter++; } catch (...) {...}—— 日志失败也会进 catch,干扰判断 - ✅ 正确做法:仅包裹
item.validate()和saveToDB(item),前置日志和后置计数独立执行 - 建议每个 try 块控制在 3~5 行逻辑内,对应一个明确的“风险动作”
用结构化错误容器替代裸列表收集
别用 List<string> errors = new ArrayList()</string> 随意攒错误信息。构建轻量错误对象:
- 包含字段:序号、原始数据摘要(如 orderNo)、异常类型、简明原因、时间戳
- 示例:
new FailRecord(i, item.getOrderNo(), "VALIDATE_FAILED", "phone is empty", Instant.now()) - 批量结束后统一输出为 JSON 或写入 error-topic,便于下游做补偿或人工核查
性能与可观测性兼顾的小技巧
百万级循环中,异常处理本身不能成瓶颈:
- 避免在 catch 中做耗时操作(如同步写磁盘日志、远程调用)——改用异步日志框架(如 Logback 的 AsyncAppender)
- 对高频异常(如每千条出现一次的校验失败)做采样记录,非 100% 全量打点
- 在循环外维护一个
AtomicInteger failCount,每触发一次有效拦截就 +1,方便实时监控失败率










