文章 557
评论 5
浏览 200528
批处理任务内存 OOM:一次性加载百万数据?流式查询+分片处理+游标分批提交

批处理任务内存 OOM:一次性加载百万数据?流式查询+分片处理+游标分批提交

公司的日报任务每天半夜 2 点跑,统计前一天所有订单的销售额。代码逻辑很简单——SELECT * FROM orders WHERE date = '2026-06-30',然后 for 循环逐条累加。平时几万条订单没问题,大促那天 300 万条订单,SELECT * 把 300 万行全部加载到了 JVM 堆里,直接 OOM。运维半夜被电话叫醒,手动分批 SQL 跑了一个小时才完成。 批处理的 OOM 跟文件下载的 OOM 原因一样——把全部数据一把梭进内存。 数据库能存那么大的表,不代表你的 JVM 堆能装下它。今天聊聊三种让批处理不再 OOM 的方法:流式查询、分片处理、游标分批提交。 问题:JDBC 默认是一次性加载全部结果集 MySQL 的 JDBC 驱动默认行为是:执行 SELECT 后,把所有结果行都拉到客户端内存里。你的代码写的虽然是 while (rs.next()),但在 next() 之前,数据已经在内存里了。 JDBC 默认行为: → 执行 SELECT → 100 万行全部加载到 ResultSet(JVM 堆) → while (rs.next()) 遍历....

@Async 线程上下文丢失:MDC 链路追踪断裂?TransmittableThreadLocal 透传实战

@Async 线程上下文丢失:MDC 链路追踪断裂?TransmittableThreadLocal 透传实战

公司排查一个异步任务异常,在 ELK 里搜对应的 requestId,搜出来的日志只有主线程的。异步线程里的日志一条都没搜到——因为 MDC 里的 requestId 根本没有传到异步线程里。异步线程打出来的日志,requestId 是空的。一整条调用链在异步这个节点断掉了。 问题很直接:@Async 用一个线程池执行任务,但 MDC 是基于 ThreadLocal 的,ThreadLocal 不会自动从主线程传到子线程。异步线程里 MDC.get("requestId") 返回 null,日志里就少了这条 key。 今天聊聊怎么用 TransmittableThreadLocal(TTL)把主线程的上下文透传到异步线程里。 为什么 ThreadLocal 不行,TTL 就行 ThreadLocal 的一个基本特性:子线程拿不到父线程的值。 主线程里 MDC.put("requestId", "abc123"),然后 @Async 开一个新线程去执行,新线程里 MDC.get("requestId") 是 null。 一个直觉的解法是调用线程池时手动传参数。把 requestId ....

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程