ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MyBatis Plus 大数据量查询优化:TaoToken 统一 Key 下的分页与流式读取实战

MyBatis Plus 大数据量查询优化:TaoToken 统一 Key 下的分页与流式读取实战 1. 百万级数据查询为什么会把服务拖垮单表一百万行用 MyBatis Plus 的Page一页一页查看起来没问题但真跑起来你会发现两件事第一LIMIT 1000000, 20这种深分页会让 MySQL 扫描并丢弃前面一百万行越翻越慢第二如果你图省事把size设成 50 万JVM 里瞬间多出几十万个实体对象GC 直接飙红运气不好就是OutOfMemoryError。我在一个数据导出需求里踩过这个坑接口传size100000本地测试 20 万数据没事上生产 300 万数据直接 OOM日志里全是GC overhead limit exceeded。后来拆成流式读取才稳住。大数据量查询的典型场景其实就三类数据迁移、数据导出、批量处理。这三类的共同点是——你不需要一次性拿到全部结果你需要的是「稳定地把每一行处理掉」。常规查询把结果集全塞进内存流式查询用迭代器一行一行取游标查询用fetchSize控制每次取多少行。三者内存曲线完全不同常规查询内存随记录数线性增长流式查询内存基本平稳只取决于批处理大小。这篇要解决的就是在 MyBatis Plus 里怎么把分页、流式、游标三种方式配好并且用 TaoToken 统一 Key 把调用链路包括后续可能接入的模型辅助处理串起来让整个大数据量查询优化方案可复制、可压测、可排障。先说清楚适合谁看如果你正在写数据导出、对账、报表预计算这类要扫全表的任务或者你的分页接口在百万数据下已经慢到不可接受那这篇的配置和压测步骤可以直接拿去用。如果你只是查几千行做列表展示常规分页就够了不用上流式。核心检索词先摆出来MyBatis Plus 大数据量查询优化重点在分页插件配置、流式查询Cursor、游标fetchSize三件事外加一个统一 Key 的 API 通道做辅助处理。下面从环境准备开始一步步给可复制的配置。2. TaoToken 统一 Key 的前置准备与接入配置为什么大数据量查询要扯到 TaoToken因为很多导出任务不只是查库还要对每一行做清洗、分类、摘要比如把用户评论批量做情感标注或者把日志行做异常归类。这些处理如果每行都去调一次模型Key 管理会乱成一团。TaoToken 提供统一 Key 和统一 API 通道你只需要在配置里维护一份 Base URL 和 Key代码里不用散落多个厂商的密钥。TaoToken 是什么它是一个统一的大模型 API 接入通道把不同模型的调用收敛到一套 Key 和一套 OpenAI 兼容接口上。能做什么你可以在数据处理的批任务里用同一个 Key 调用对话模型做文本处理不用为每个模型单独申请和轮换密钥。适合谁需要在大数据量管道里嵌入模型处理、又不想管理一堆 Key 的开发者。接入前先拿 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 列表在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。API 基础地址统一用 https://taotoken.net/api 注意这个地址不加 UTM 参数直接写进配置即可。如果你用的是 Claude Code 这类编码工具做辅助开发可以参考接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Base URL、Key、Model ID 三件套的完整说明。想先验证模型通不通可以去模型对话页 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 。这里要强调一个原则TaoToken 是 API 通道不是数据库连接池也不是 MyBatis 的替代品。它负责的是「查询出来之后的数据怎么调模型处理」数据库查询本身还是走 MyBatis Plus。两者职责分清配置才不会乱。拿 Key 的步骤很简单登录后进控制台点创建 Key复制出来存到环境变量里别硬编码进代码。下面第三节会给完整的配置文件片段包括 MyBatis Plus 的分页插件和 TaoToken 的客户端配置。3. 可复制的 MyBatis Plus 分页与流式配置片段这一节是核心直接给能粘贴的配置。先配 MyBatis Plus 的分页插件再配流式查询的 Mapper最后配 TaoToken 的客户端。先看分页插件。MyBatis Plus 3.5.x 之后推荐用MybatisPlusInterceptor加PaginationInnerInterceptor并且要指定数据库类型否则分页 SQL 方言可能不对。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); // 单页最大限制防止有人传 size1000000 把内存打爆 pagination.setMaxLimit(1000L); // 溢出总页数后是否进行处理这里设为 false超出直接返回空 pagination.setOverflow(false); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit(1000L)这行很关键。它强制单页最多 1000 条从源头挡住「一次查一百万」的请求。如果你的导出任务确实需要更多不要调大这个值而是改用流式查询。接着配流式查询的 Mapper。MyBatis 提供org.apache.ibatis.cursor.Cursor接口它继承Closeable和Iterable可以遍历也可以关闭。注意流式查询会独占数据库连接取完必须关闭。Mapper public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity { // 流式查询返回 Cursor一行一行取 Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment}) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize Integer.MIN_VALUE) CursorBigDataSearchEntity streamList(Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity wrapper); // 游标查询通过 ResultHandler 处理一次取 fetchSize 条 Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment}) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000) ResultType(BigDataSearchEntity.class) void cursorList(Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity wrapper, ResultHandlerBigDataSearchEntity handler); }这里有个 MySQL 特有的坑fetchSize要设成Integer.MIN_VALUE才能让 MySQL 真正开启流式读取。如果你设成 1000MySQL 驱动默认还是会把结果全拉到客户端。这是 MySQL JDBC 驱动的行为和 Oracle 不一样。Oracle 设fetchSize1000就是一次取 1000 条MySQL 必须用Integer.MIN_VALUE才触发逐行返回。游标查询的fetchSize1000配合ResultHandler适合「一次处理一批」的场景。ResultHandler里可以写批处理逻辑比如每 1000 条写一次文件或调一次模型。然后是 TaoToken 的客户端配置。用环境变量存 Key配置文件里只写占位。# application.yml taotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model-id: your-model-id timeout: 30000对应的 Java 配置类Configuration ConfigurationProperties(prefix taotoken) public class TaoTokenConfig { private String baseUrl; private String apiKey; private String modelId; private int timeout; // getter/setter 省略 }如果你用 Claude Code 做辅助开发它的 settings 配置里 Base URL 填https://taotoken.net/apiKey 填你的 KeyModel ID 按文档填。三件套缺一不可只填 Base URL 不填 Model ID 会报模型不存在。配置齐了之后分页查询用Page流式查询用Cursor游标查询用ResultHandler三条路各管各的场景。下一节验证请求。4. 验证请求与压测从 401 到成功读取配置写完先别急着上百万数据用一万行验证链路通不通。先验证 TaoToken 的 Key 是否有效再验证 MyBatis Plus 的流式查询是否真的不占内存。验证 TaoToken 最简单的方式是发一个对话请求。用 curlcurl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: your-model-id, messages: [{role: user, content: ping}] }返回 200 且有choices字段说明 Key 和通道都正常。如果返回 401看第五节排障。验证流式查询写一个测试方法用Cursor遍历一万行同时打印内存占用Test public void testStreamQuery() { QueryWrapperBigDataSearchEntity wrapper new QueryWrapper(); wrapper.le(id, 10000); long before Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); try (CursorBigDataSearchEntity cursor bigDataSearchMapper.streamList(wrapper)) { int count 0; for (BigDataSearchEntity entity : cursor) { // 模拟处理 count; if (count % 1000 0) { long now Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.println(已处理 count 条当前内存增量 (now - before) / 1024 / 1024 MB); } } System.out.println(总计 count 条); } catch (IOException e) { throw new RuntimeException(e); } }实测下来一万行流式读取内存增量稳定在几 MB不会随记录数增长。如果你把同样的查询改成selectList一次性返回内存增量会到几十 MB。压测步骤把数据量逐步加到 10 万、50 万、100 万观察三个指标——接口响应时间、JVM 老年代占用、GC 频率。分页查询在深分页时响应时间会明显上升流式查询的响应时间基本平稳因为它是边取边处理。游标查询的验证类似用ResultHandler收集计数AtomicInteger counter new AtomicInteger(); bigDataSearchMapper.cursorList(wrapper, resultContext - { BigDataSearchEntity entity resultContext.getResultObject(); counter.incrementAndGet(); }); System.out.println(游标处理 counter.get() 条);成功的结果是分页查询单页 1000 条在 100 万数据下响应时间可控流式查询内存平稳游标查询批处理不丢数据。如果哪一步不对看下一节。5. 常见报错排查401、local proxy failed 与 OOM这一节对照真实报错逐个拆。401 UnauthorizedTaoToken 返回 401九成是 Key 没传对。检查三处——环境变量TAOTOKEN_API_KEY是否真的导出到运行环境请求头是不是Authorization: Bearer key注意 Bearer 后面有空格Key 有没有多余换行。如果你在 Claude Code 里配检查 settings 里的 Key 字段有没有被引号包错。local proxy failed这个报错通常出现在本地调试时请求发不出去。先确认base-url写的是https://taotoken.net/api不要多写或少写/v1。有些客户端会自动拼/v1/chat/completions你只需要给到/api。再确认本机网络能正常访问外网公司内网如果有出口限制找运维开白名单。reading choices 报错返回体里没有choices字段一般是 Model ID 填错了。去接入文档核对当前可用的 Model ID别用猜测的名字。另外检查请求体 JSON 是否合法messages数组不能为空。OOM 内存溢出如果你用了流式查询还是 OOM检查两点。第一fetchSize在 MySQL 下是不是设成了Integer.MIN_VALUE设成别的值 MySQL 会把结果全拉回来。第二Cursor有没有用 try-with-resources 关闭没关闭的话连接和结果集一直挂着内存只增不减。Cursor 遍历时抛异常流式查询期间不能对同一个连接发其他查询否则会报「Streaming result set is still active」。解决办法是把流式查询的结果先处理完或关闭再做别的数据库操作。分页插件不生效检查MybatisPlusInterceptor有没有注册成 Bean以及PaginationInnerInterceptor有没有指定DbType.MYSQL。如果用的是多数据源每个数据源都要单独配。CC Switch / Cline MCP / Codex auth.json 配置如果你在这些工具里接 TaoToken三件套必须写全——Base URL 填https://taotoken.net/apiKey 填你的 KeyModel ID 填文档里的模型名。Codex 的auth.json里字段名要对Base URL 和 Key 分开写别混在一个字段里。排障的核心思路是先确认 Key 和 Base URL 对再确认 Model ID 对最后确认网络通。三步走完大部分报错都能定位。6. 把统一 Key 用在数据处理管道里回到大数据量查询的完整链路MyBatis Plus 负责从库里稳定取数TaoToken 负责对取出来的数据做模型处理。两者用统一 Key 串起来代码里只维护一份配置。具体做法是在ResultHandler或Cursor的遍历循环里每攒够一批比如 100 条就调一次 TaoToken 的接口做批量处理。这样既不会每行一次请求把 QPS 打满也不会一次性攒太多把内存撑爆。批大小根据你的模型响应时间和内存情况调一般 50 到 200 之间比较稳。如果你要长期跑这类任务Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有更详细的用量说明。想先验证模型效果模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 可以直接试。Key 管理和文档分别在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 和 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后给一个实用技巧流式查询的Cursor一定要放在 try-with-resources 里别手动 close 漏了。游标查询的fetchSize在 MySQL 下和 Oracle 下行为不同迁移数据库时记得改。分页插件的maxLimit设一个合理值从源头挡住大页请求。这三条守住百万级数据查询基本不会出大问题。
返回列表