还在用分页?试试MyBatis流式查询,强的一批!

发布时间:2026/9/28 18:23:38

还在用分页?试试MyBatis流式查询,强的一批! 1. 百万行导出把服务打挂的那一夜先说结论MyBatis 流式查询Cursor / ResultHandler能让你在百万行数据导出时把 JVM 内存压成一条平线而传统分页在深分页场景下既慢又容易把数据库连接池拖垮。这篇就围绕 MyBatis 大结果集场景把分页和流式查询的内存、响应差异讲透给出 Cursor 和 ResultHandler 两种写法的可复制配置再演示怎么用 TaoToken 统一 Key/API 通道接入 AI 工具辅助生成 Mapper 骨架目标是跑通百万行导出不 OOM。如果你做过数据迁移、报表导出、批量对账这类需求大概率遇到过这种场面本地跑得好好的一上生产查 100 万行服务直接 OOM或者数据库 CPU 飙到 100%接口超时。很多人第一反应是加分页LIMIT 0,1000、LIMIT 1000,1000一页页捞。分页不是不能用但深分页的LIMIT 1000000,1000会让 MySQL 扫描并丢弃前 100 万行越翻越慢而且每页都要重新建立结果集框架还要把每页结果封装成实体对象时间和内存双重浪费。流式查询换了个思路查询成功后不返回集合而是返回一个迭代器应用每次从迭代器取一条或一批结果。数据库连接保持打开服务端游标逐行吐数据客户端内存只跟批处理大小有关不跟总行数有关。这就是百万行导出不 OOM 的核心。下面从场景对比开始一步步把配置和验证动作落地。2. 分页 vs 流式内存与响应到底差在哪2.1 常规分页查询的写法与代价假设单表 100 万行用 MyBatis-Plus 分页查询大概长这样Mapper public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity { Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ) PageBigDataSearchEntity pageList(Param(page) PageBigDataSearchEntity page, Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity queryWrapper); }这段代码本身没问题问题在调用方式。如果你在循环里一页页查每页 1000 条查 1000 次每次都要走一遍 SQL 解析、结果集封装、对象映射。深分页时LIMIT偏移量越来越大数据库要扫描并丢弃前面的行响应时间线性上涨。更糟的是如果某一页不小心设成 10 万条JVM 里瞬间多出 10 万个实体对象GC 压力陡增。我试过在测试环境把单页调到 50 万堆内存 2G 的实例直接 Full GC 频繁接口响应从秒级掉到分钟级。分页适合的是「总量可控、页大小合理、能走覆盖索引」的场景一旦总量上百万且要全量处理分页就不是最优解。2.2 流式查询为什么能稳住内存流式查询的内存曲线是平的。非流式查询内存随记录数近乎直线增长流式查询内存大小取决于批处理大小fetchSize这个值越大内存占用越高所以要根据业务设置合适大小。MyBatis 提供了org.apache.ibatis.cursor.Cursor接口它继承了java.io.Closeable和java.lang.Iterable也就是说 Cursor 可关闭、可遍历还提供三个方法isOpen()取数据前判断 Cursor 是否打开只有打开才能取数据isConsumed()判断查询结果是否全部取完getCurrentIndex()返回已经获取了多少条数据。用流式查询有两个必须记住的点。第一执行流式查询后数据库访问框架不负责关闭连接需要应用在取完数据后自己关闭。第二必须先读取或关闭结果集中的所有行然后才能对连接发出任何其他查询否则会抛异常。因为流式查询会独占连接所以必须尽快处理完。2.3 两种流式写法的定位差异MyBatis 流式查询常见两种落地方式。一种是 Cursor 游标查询Mapper 方法直接返回CursorT调用方用try-with-resources遍历适合自己控制读取节奏、边读边写的场景。另一种是 ResultHandlerMapper 方法返回void通过Options配fetchSize和resultSetType再传一个ResultHandler处理每条结果适合把处理逻辑内聚到回调里。两者底层都依赖服务端游标和fetchSize。区别在于 Cursor 把迭代器交给调用方ResultHandler 把每条结果推给回调。选哪个看你的处理逻辑是「拉」还是「推」。下面两节分别给出可复制配置。3. TaoToken 前置统一 Key 与 API 通道在写 Mapper 骨架之前先花几分钟把 AI 辅助通道准备好。TaoToken 提供统一的 Key 和 API 通道官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。它的作用是让你用一套 Key 就能调用多种模型能力不用在多个平台之间来回切换配置。具体操作路径先到控制台创建 API Key控制台地址 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面生成密钥页面地址 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后如果你只是想先验证模型能不能正常对话可以去模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 试一句。如果你打算长期用 AI 辅助编码、跑 Agent 任务建议看 Coding Plan地址 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 按套餐走比单次调用更划算。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了不同语言和工具的配置方式。如果你用的是 Claude Code 这类工具参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 的说明配置即可。把 Key 配好之后后面让 AI 生成 Mapper 骨架、补全Options参数、解释报错都能直接走这个通道。注意Key 属于敏感凭证不要硬编码进提交到仓库的配置文件建议用环境变量或配置中心注入。4. 可复制配置Cursor 与 ResultHandler 两种写法4.1 Cursor 游标查询完整配置先看 Cursor 写法。Mapper 接口返回CursorTMapper public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity { Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000) CursorBigDataSearchEntity streamList(Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity queryWrapper); }resultSetType ResultSetType.FORWARD_ONLY表示结果集游标只能向下滚动这是流式查询的常见配置。fetchSize 1000表示每次从服务端取 1000 条这个值决定内存占用不要盲目设成 100 万。调用方这样写Service public class ExportService { Resource private BigDataSearchMapper bigDataSearchMapper; public void exportAll(QueryWrapperBigDataSearchEntity wrapper) { try (CursorBigDataSearchEntity cursor bigDataSearchMapper.streamList(wrapper)) { cursor.forEach(entity - { // 逐条处理比如写入文件、发送到下游 writeToFile(entity); }); } catch (IOException e) { throw new RuntimeException(导出失败, e); } } }try-with-resources保证 Cursor 用完自动关闭连接随之释放。这里的关键是不要在forEach里再发起其他数据库查询因为流式查询独占连接中途发新查询会抛异常。如果确实要查别的表先把数据读出来暂存或者用独立的连接。4.2 ResultHandler 回调写法完整配置ResultHandler 写法把处理逻辑放进回调Mapper 方法返回voidMapper public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity { Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000) ResultType(BigDataSearchEntity.class) void listData(Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity queryWrapper, ResultHandlerBigDataSearchEntity handler); }注意返回类型必须是void因为查询结果在ResultHandler里处理。调用方用 lambda 实现处理逻辑Service public class BatchProcessService { Resource private BigDataSearchMapper bigDataSearchMapper; public void processAll(QueryWrapperBigDataSearchEntity wrapper) { bigDataSearchMapper.listData(wrapper, resultContext - { BigDataSearchEntity entity resultContext.getResultObject(); // 逐条处理 handle(entity); }); } }ResultType(BigDataSearchEntity.class)告诉 MyBatis 把每行转换成哪个实体。fetchSize同样控制每次获取量。ResultHandler 的好处是处理逻辑内聚不用自己管 Cursor 的关闭框架会处理。但要注意回调里同样不要发起新的数据库查询。4.3 fetchSize 与 resultSetType 参数对照参数可选值作用建议resultSetTypeFORWARD_ONLY游标只能向下滚动流式查询首选resultSetTypeSCROLL_INSENSITIVE游标可上下移动数据库变化时结果集不变一般不用resultSetTypeSCROLL_SENSITIVE游标可上下移动数据库变化时结果集同步改变一般不用fetchSize正整数每次从服务端获取的行数按内存和网络调1000 起步fetchSize不是越大越好。设得太大客户端一次缓存太多行内存又上去了设得太小网络往返次数多整体变慢。1000 到 5000 是比较稳的区间具体看单行数据大小和网络状况。5. 验证请求与成功结果5.1 用 AI 辅助生成 Mapper 骨架配置好 TaoToken 通道后可以让 AI 帮你生成 Mapper 骨架省去手写注解的功夫。比如在模型对话页输入这样的提示帮我写一个 MyBatis Mapper 接口查询 big_data_search 表全量数据 要求使用流式查询返回 CursorBigDataSearchEntity 配置 resultSetType 为 FORWARD_ONLYfetchSize 为 1000 使用 MyBatis-Plus 的 QueryWrapper 作为条件参数。AI 会返回类似第 4.1 节的代码。拿到之后自己核对一遍注解和泛型确认Options参数符合预期。这一步的价值在于减少样板代码的拼写错误尤其是ResultSetType这种容易记混的枚举。生成完骨架再让它补一段调用示例和单元测试思路能省不少时间。5.2 百万行导出的验证动作验证流式查询是否真的不 OOM可以按这个步骤来。第一步准备一张 100 万行的测试表用存储过程或批量插入造数据。第二步把 JVM 堆内存限制到 512M启动参数加-Xmx512m。第三步分别跑分页全量查询和流式查询用jconsole或VisualVM观察堆内存曲线。分页全量查询在堆内存曲线上会看到明显的锯齿每次翻页内存上涨再回落深分页时响应时间越来越长。流式查询的堆内存曲线基本是平的只在fetchSize批次处理时有小幅波动。第四步统计总耗时和 GC 次数。实测下来百万行导出场景流式查询的 Full GC 次数通常是个位数分页则可能几十次甚至上百次。5.3 成功结果的特征跑通之后你会看到这些现象接口在几分钟内处理完 100 万行堆内存没有持续上涨没有 OOM 异常数据库连接在try-with-resources结束后正常归还连接池。日志里能看到getCurrentIndex()递增到 100 万说明数据全部读完。如果中途抛了Streaming result set is still active之类的异常说明你在流式读取过程中发起了新的查询需要调整处理逻辑。6. 本篇常见错排查6.1 流式查询中途发新查询报错最常见的报错是流式结果集还没读完就发起了新查询。原因是流式查询独占数据库连接MyBatis 不允许在同一个连接上同时有两个活跃结果集。解决办法是把新查询的数据提前查出来或者用独立的数据源连接。如果处理逻辑里必须查关联表考虑先批量把关联数据加载到本地缓存再在流式遍历时从缓存取。6.2 fetchSize 设置无效有人发现设了fetchSize但内存还是涨。先确认resultSetType是不是FORWARD_ONLY如果不是MySQL 驱动可能不会启用真正的流式读取。另外MySQL 驱动需要配合useCursorFetchtrue参数才能让fetchSize生效连接串里加上这个参数jdbc:mysql://localhost:3306/test?useCursorFetchtrueuseSSLfalse如果用的是其他数据库参数名可能不同查对应驱动的文档确认。6.3 Cursor 忘记关闭导致连接泄漏Cursor 实现了Closeable但如果你手动new或者没有用try-with-resources忘记关闭就会导致连接泄漏。连接池被占满后后续请求全部阻塞。排查方法是看连接池活跃连接数是否持续上涨不回落。修复方式很简单所有 Cursor 都用try-with-resources包起来或者用 ResultHandler 写法让框架管理。6.4 分页深分页慢的替代方案如果你暂时不想改流式深分页也有优化空间。比如用游标分页基于上次查询的最大 ID 继续查避免LIMIT大偏移量。但游标分页要求有单调递增的排序字段且不能跳页。对于全量导出这种场景流式查询还是更直接。如果只是接口分页展示游标分页够用。6.5 处理逻辑太慢拖垮整体流式查询把数据读出来后如果每条处理逻辑很重比如每条都写一次数据库整体会很慢。这时候要改成批量处理攒够一批再写。注意攒批的容器要及时清空避免临时容器本身变成内存泄漏点。批大小和fetchSize配合调通常批大小等于或略小于fetchSize比较合适。7. 接入文档与工具分流排障和接入相关的问题优先看 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 和接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面覆盖了 Key 管理、参数配置、常见错误码。想先验证模型输出质量去模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 直接试。长期做编码辅助、跑 Agent 任务看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。Claude Code 用户参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。最后留一个实操建议把fetchSize和批处理大小做成配置项不同环境用不同值。测试环境可以设小一点方便观察生产环境根据单行大小和内存上限调。流式查询不是银弹它适合全量遍历、数据迁移、批量导出这类场景不适合需要随机跳页的交互式分页。选对场景百万行导出不 OOM 就是水到渠成的事。
延伸阅读

更多相关文章

2026/9/28 19:18:42

LabelMe JSON转YOLO格式:坐标语义重映射实战指南

简介:本资源是一款专为计算机视觉开发者设计的LabelMe标注数据转YOLO格式的轻量级转换工具,面向已使用LabelMe完成图像分割标注、亟需适配YOLO系列模型(如YOLOv5 v7.0)训练流程的初/中级算法工程师与科研实践者。工具支持批量JSON…

2026/9/28 19:18:42

LabelMe JSON转YOLO格式:从数据结构到可验证转换链

简介:这是一份面向计算机视觉初学者与YOLO模型实践者的轻量级数据格式转换工具包,专为解决LabelMe标注的分割数据集难以直接用于YOLO系列模型训练的痛点而设计。资源提供完整的命令行脚本(labelme2yolo.py)及配套说明,…

2026/9/28 19:18:42

APS6404 PSRAM实战:MCU外扩8MB内存的完整指南

说实话,第一次在货架前看到 APS6404L-SQH-SN 的时候,我差点以为它就是一颗普通的 SPI NOR Flash——SOIC-8 封装、8 个引脚,长得跟 W25Q64 亲兄弟似的。但后来查手册才发现,这颗芯片看着像 Flash,内核其实是 DRAM&…

2026/9/28 19:13:42

如何下载Eclipse

本篇博文适用于第一次下载eclipse的小白(绝对不是在完成java课程作业) 1.首先登陆 Eclipse 官网(Eclipse Downloads | The Eclipse Foundation) 2.点击如上图所示“Install your favorite desktop ide packages”栏目中的downloa…

2026/9/28 3:03:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/28 6:05:15

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/28 6:07:41

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/28 0:02:03

广州外贸网站建设推广:从零搭建全流程拆解与真实报价避坑

广州外贸网站建设推广:从零搭建全流程拆解与真实报价避坑 改个需求建站公司拖一周,后台改个文案还得再交一笔“技术维护费”。这种憋屈事儿,做外贸的朋友太熟悉了。很多老板在找广州外贸网站建设推广服务商时,光盯着首页好不好看,却忽略了从零搭建一个能…

2026/9/28 0:02:04

搞懂百度竞价推广价格,网站性能优化别掉链子

搞懂百度竞价推广价格,网站性能优化别掉链子 网站突然打不开,浏览器弹出红色警告“此网站存在安全风险”,后台一看全是乱码代码和奇怪的跳转链接。这种网站被黑挂马的绝望感,很多刚转行做网站的朋友都经历过,尤其是那些为了省几百块钱服务器费用的新手。…

2026/9/25 20:55:38

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/26 19:58:38

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/28 1:59:25

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑