SpringBoot+Azure SQL Server查询10万条记录报Connection reset问题
问题分析与解决指南
首先从异常栈可以明确:你配置queryTimeout解决不了问题,因为故障根本不在数据库查询环节。
异常抛出点在Jackson序列化响应、往客户端写数据的阶段(栈里明确能看到UTF8JsonGenerator._flushBuffer、OutputBuffer.realWriteBytes的调用链),抛出的ClientAbortException: Connection reset by peer本质是连接被对端主动断开,和SQL执行超时没有关系。
先修正代码里的明显用法错误
你当前JdbcTemplate的RowCallbackHandler实现是错的:RowCallbackHandler的设计逻辑是JdbcTemplate遍历结果集时,每读到一行就回调一次processRow方法,不需要你手动写while (resultSet.next())做循环。你现在的写法会在第一次触发回调时就把整个结果集全部遍历完,后续回调拿到的ResultSet已经是遍历结束的状态,虽然当前精简代码逻辑碰巧能跑,但不符合组件设计预期,还会放大内存占用问题。
正确的写法如下:
List<TableName> tnames = new ArrayList<>(); jdbcTemplate.query("select * from testdb.table_name", (RowCallbackHandler) resultSet -> { // 不要加while循环,框架已经处理了逐行遍历逻辑 TableName tn = new TableName(); tn.setStatus(resultSet.getString("status")); tnames.add(tn); // 单行处理逻辑 }); return tnames;
如果需要手动控制结果集遍历逻辑,应该改用ResultSetExtractor,不要在RowCallbackHandler里自己循环ResultSet。
核心故障原因排查方向
你测试到返回行数超过47000就报错,本质是行数达到阈值后,整个请求的全链路耗时/资源占用超过了临界值,按优先级排查以下点:
- 链路超时被截断
你测的SQL在数据库端执行就要12-17秒,这还没算JDBC驱动跨网络从Azure SQL拉取数据到应用、应用把全量数据加载到内存、序列化成JSON的时间,总耗时很容易超过前置链路的超时阈值:- 如果服务前面挂了Azure应用网关、API管理、Front Door这类七层代理,默认请求超时通常是30秒,部分自定义配置可能更短
- 调用方(前端、下游服务)本身设置的请求超时如果小于接口总耗时,会主动断开连接,直接抛出你看到的连接重置错误
- 47000行刚好是总耗时触达超时阈值的临界点,行数更少的时候总耗时没超就能正常返回
- 内存占用导致的停顿
几万行数据全量加载到JVM堆内存存成List,再一次性序列化成大JSON串,会生成大对象占用堆内存:- 如果JVM堆配置不足,会触发长时间Full GC,导致应用暂停无法往连接写数据,对端会判定连接失效主动断开
- Tomcat默认响应缓冲区只有8KB,大响应需要多次刷出缓冲区,如果GC停顿时间过长,同样会触发连接断开
- JDBC驱动拉取模式不合理
SQL Server的JDBC驱动默认会一次性把全量结果集拉到应用侧内存,不是逐行流式读取,你感知到的12-17秒耗时里,有很大一部分是跨网从Azure拉数据的时间,进一步拉长了整体请求耗时。
可落地的解决方案
按改造成本从低到高选择:
快速修复方案
- 先修正前面提到的
RowCallbackHandler用法错误,避免结果集处理逻辑异常 - 统一调整全链路超时配置:把七层网关、负载均衡、调用端的请求超时阈值调到60秒以上,长查询接口建议走异步Servlet处理,避免占用容器工作线程
- 给JDBC连接串加参数
selectMethod=cursor,启用游标模式流式读取结果集,不要一次性把全量数据加载到应用内存,降低内存占用和数据拉取等待时间 - 适当调大服务的JVM堆内存,避免大结果集触发频繁Full GC
长期最优方案
- 普通业务接口禁止一次性返回10万行数据,改成分页查询,单次返回1000-5000行,是稳定性最高的方案
- 如果是全量数据导出场景,不要走普通JSON同步接口:改成异步任务模式,接口接收到请求后立即返回任务ID,后台异步跑查询生成CSV/Excel文件存到对象存储,用户凭任务ID下载文件,完全避开长连接超时问题
- 必须同步返回大结果集的场景,用Jackson流式序列化API,边读数据库边往响应输出流写JSON,不需要把全量数据存在List里占内存,同时能第一时间返回响应头,避免连接被判定为空闲断开。
内容的提问来源于stack exchange,提问作者Dnyaneshwar Jadhav
相关产品推荐
相关产品推荐

