MyBatis流式查询需关闭自动提交并配置JDBC游标。必须使用ExecutorType.SIMPLE、禁用事务(@Transactional(propagation = Propagation.NOT_SUPPORTED))、设置useCursorFetch=true和defaultFetchSize=1000,禁用LIMIT与RowBounds,调整连接池超时参数。

MyBatis流式查询必须关掉自动提交
MyBatis默认用ExecutorType.SIMPLE,查完就缓存整个结果集到内存,哪怕你写了ResultHandler也没用——流式根本没生效。关键动作是显式指定ExecutorType.SIMPLE并关闭事务自动提交。
- 在
SqlSessionTemplate或SqlSessionFactoryBean配置里加executorType="SIMPLE"(Spring Boot 2.3+ 可用mybatis.configuration.default-executor-type=simple) - 手动获取
SqlSession时,必须用sqlSessionFactory.openSession(ExecutorType.SIMPLE, false),第二个参数false才是重点:不自动commit,才能保持游标打开 - 如果用了
@Transactional,它会强制开启事务并自动commit,直接废掉流式——导出接口务必加@Transactional(propagation = Propagation.NOT_SUPPORTED)
ResultHandler里别存数据,直接写文件或响应流
常见错误是把每条记录add进ArrayList,等处理完再统一写,这等于又回到内存全量加载。流式的意义就是“来一条、写一条、丢一条”。
- 导出Excel用
Apache POI SXSSFWorkbook,每100行row.flush(),避免Sheet缓存撑爆堆 - 写CSV直接用
OutputStreamWriter+BufferedWriter,字符编码设为UTF-8,别用String.concat拼接大字段 - Spring MVC中返回
ResponseEntity<streamingresponsebody></streamingresponsebody>,在lambda里写outputStream,记得flush()但别close()(由框架关)
MySQL驱动要配streaming参数,否则还是全量拉
MyBatis只是调度层,底层JDBC驱动不配合,照样OOM。MySQL Connector/J 默认把结果集全读进内存,必须显式启用流式游标。
- 连接URL加参数:
?useCursorFetch=true&defaultFetchSize=1000(注意&要转义) -
useCursorFetch=true让驱动用服务器端游标,defaultFetchSize=1000控制每次从服务端取多少行,值太小IO多,太大内存压力大,1000是较稳的起点 - 确认驱动版本≥5.1.38 或 ≥8.0.16,老版本
useCursorFetch无效,会静默降级成普通查询 - 执行前检查
ResultSet.getFetchSize()是否返回1000,不是说明配置没生效
分页查询和流式查询不能共存
有人想“先count再limit流式”,这是典型误区。LIMIT会让MySQL无法使用游标,ORDER BY + LIMIT也会触发临时表,彻底破坏流式能力。
- 流式只适用于无offset的全量扫描,比如按主键
WHERE id > ? ORDER BY id做游标分段 - 真要支持“跳过前10万行再导出”,得用
id > last_id方式分批查,每次查1万行,用上一批最后id当起点 - 别在SQL里写
LIMIT #{offset}, #{size},MyBatis的RowBounds也一样,它们和流式互斥
最易被忽略的是连接池配置:HikariCP默认connection-timeout=30000,而百万行导出可能耗时几分钟,必须调大connection-timeout和idle-timeout,不然中途连接被池回收,ResultSet is closed就来了。










