单次大sql查询未加限制会触发jvm堆崩溃,根源在于resultset全量加载、数据库临时表排序失控及网络缓冲区堆积三者连锁溢出;需同时启用游标流式读取(usecursorfetch=true+setfetchsize(integer.min_value))、为order by字段建索引、调大net_buffer_length与max_allowed_packet,并设置sockettimeout。

单次大SQL查询未加限制,返回百万级记录,直接触发堆崩溃,问题不在“数据多”,而在内存区域被错误分配和持续挤占——客户端JVM堆、数据库连接缓冲区、网络传输缓冲区三者形成连锁溢出。
客户端JVM堆:ResultSet全量加载是头号杀手
默认情况下,JDBC驱动(如MySQL Connector/J)会把整个结果集一次性拉进内存。哪怕数据库只用几MB处理查询,客户端却可能为百万行分配超1GB堆空间,尤其字段含TEXT/BLOB或字符串未复用时,对象膨胀极快。此时GC频繁、Full GC卡顿、最终java.lang.OutOfMemoryError: Java heap space报错。
- 必须显式启用游标流式读取:MySQL连接串加
?useCursorFetch=true&defaultFetchSize=500;PostgreSQL需调用statement.setFetchSize(500)并确保语句不带FOR UPDATE等禁用游标的修饰 -
defaultFetchSize不是“最多取500条”,而是“每次从服务端拉500条到本地缓冲”,配合while(rs.next())逐行处理,内存峰值可压至几十MB内 - 避免
rs.getObject(i)反复创建包装对象,优先用rs.getInt(i)、rs.getString(i)等原始类型方法
数据库连接缓冲区:临时表与排序内存失控
若SQL含ORDER BY + LIMIT但排序字段无索引,MySQL会在内存中建临时表+全量排序;PostgreSQL则依赖work_mem,一旦中间结果超限,就会触发磁盘落写(slow)或直接OOM(当work_mem设得过大且并发高时)。
- 执行
EXPLAIN确认是否出现Using filesort或Using temporary;若有,给ORDER BY字段补联合索引(如(status, created_at)) - 视图里写
ORDER BY是高危操作——它强制数据库先排完再截断,LIMIT无法下推;应删掉视图内排序,由外层SQL控制 - MySQL的
tmp_table_size和max_heap_table_size建议保持一致且≤256M,防止单查询吃光全局内存
网络与驱动缓冲区:包堆积引发连接中断
即使客户端开了游标,若网络吞吐不足或驱动缓冲区配置不当,服务端仍可能把大量数据打包发往客户端,导致TCP接收窗口塞满、连接超时重置,报ERROR 2013: Lost connection to MySQL server during query——这常被误判为数据库崩了,实则是链路层缓冲击穿。
- MySQL服务端调大
net_buffer_length(默认16K)和max_allowed_packet(建议≥64M),避免分包过多 - 客户端应用层加超时控制:
socketTimeout=30000(单位毫秒),防止单请求无限挂起 - DBeaver等工具执行脚本前务必关掉“Fetch all rows”选项,否则无视fetchSize设置,硬扛全量
堆崩溃从来不是某一个环节单独失守,而是JVM堆→驱动缓冲→数据库临时区→网络栈,四层缓冲区在无节制的数据洪流下依次告急。控住源头(加LIMIT/改分页)、切好路径(开游标+建索引)、守住出口(设超时+调包长),三者缺一不可。











