mybatis游标查询和流式查询
时间: 2023-11-27 17:42:47 浏览: 387
mybatis游标查询和流式查询是处理大数据量查询时常用的两种技术。
mybatis游标查询是通过游标来逐行获取查询结果,可以有效地减少内存的占用,避免OOM(Out of Memory)的问题。在MySQL 8.0中,需要在jdbc驱动链接参数中添加"useCursorFetch=true"来开启游标查询。使用游标查询时,可以通过mapper接口的Cursor返回类型来获取查询结果。
流式查询是一种将查询结果以流的方式逐行返回的技术,也是为了处理大数据量查询时的内存占用问题。使用流式查询可以避免一次性将所有查询结果加载到内存中,而是在需要的时候逐行返回数据。流式查询可以通过JDBC原生的ResultSet来实现,也可以在MyBatis中使用游标查询进行封装。
对于大数据量的业务,使用游标查询和流式查询可以有效地降低内存占用,但是需要注意并发控制,避免数据库连接被长时间占用而导致资源浪费。
相关问题
mybatis游标查询oom
### MyBatis 游标查询 OOM 解决方案
#### 使用游标的必要性
对于普通 SQL 查询,在大多数情况下能够满足需求。然而,当面对大规模数据查询(如超过百万甚至千万级别的记录),传统的批量加载方式可能导致 Java 虚拟机发生 Out of Memory (OOM) 错误。这是因为一次性读取大量数据会占用过多内存资源[^1]。
#### 流式查询机制
为了应对这种情况,可以利用 JDBC 提供的游标功能实现流式的查询操作。这种方式允许应用程序逐行获取结果集中的每一项,而不是一次性全部载入内存中。具体来说,Oracle 和 MySQL 数据库已经在服务端实现了基于游标的偏移量管理,从而减少了客户端所需分配给这些大容量数据集的空间消耗[^2]。
#### 实现方法
在 MyBatis 中启用游标查询并不复杂:
- **Mapper 接口定义**
定义一个返回 `org.apache.ibatis.cursor.Cursor<T>` 类型的方法用于执行查询语句。
```java
public interface SalesMarketsMapper {
Cursor<ROIReport> queryROIReportsCursor(@Param("params") ROIReportQuery params);
}
```
- **Service 层调用**
Service 层可以直接调用 Mapper 方法并接收返回的结果作为游标对象来进行迭代处理。
```java
@Override
public void processLargeDataset(ROIReportQuery params){
try(Cursor<ROIReport> cursor = salesMarketsMapper.queryROIReportsCursor(params)){
while(cursor.hasNext()){
ROIReport report = cursor.next();
// 对单个report做进一步业务逻辑处理...
}
} catch(Exception e){
log.error("Error processing large dataset",e);
}
}
```
需要注意的是,并不是所有的数据库驱动都默认支持这种模式;某些情况下可能需要额外设置参数以激活此特性,例如针对 MySQL 可能需配置 `useCursorFetch=true` 参数,不过实际应用中有报告指出即使缺少这个选项也能正常工作[^3]。
另外,确保所使用的 MyBatis 版本以及相应的依赖库是最新的也很重要,因为旧版本可能存在兼容性和性能方面的问题。
最后提醒一点,虽然使用游标可以在一定程度上缓解因海量数据带来的压力,但在设计阶段还是应该尽可能优化查询条件,减少不必要的全表扫描等情况的发生。
mybatis-plus的流式读取
### MyBatis-Plus 流式读取大数据防止 Out Of Memory 的解决方案
在处理大规模数据查询时,传统的全量加载方式可能会导致内存占用过高甚至触发 `OutOfMemoryError`。为了有效应对这一挑战,MyBatis-Plus 提供了流式读取功能,允许逐条或批量获取数据库中的记录,从而显著降低内存压力。
---
#### 一、MyBatis-Plus 流式读取的核心原理
MyBatis-Plus 的流式读取基于 JDBC 的游标机制实现。通过设置合适的 `ResultSetType` 和 `fetchSize` 参数[^1],可以控制结果集的行为以及每次从数据库中提取的行数。这种方式避免了一次性将所有数据加载到内存中,而是按照一定步长逐步读取数据。
---
#### 二、实现步骤
##### 1. 配置 SQL 查询语句
在 XML 映射文件中定义查询语句时,需显式指定 `fetchSize` 属性。例如:
```xml
<select id="listTaskResultIpInfo" fetchSize="1000" resultType="String">
SELECT info FROM task_result_info WHERE project_id = #{projectId} AND task_id = #{taskId}
</select>
```
此处设置了 `fetchSize=1000`,表示每次从数据库中取出 1000 行数据[^3]。
##### 2. 使用 BaseMapper 接口的方法
MyBatis-Plus 提供了一个便捷的方式——`BaseMapper.selectList` 方法支持 ResultHandler 的回调模式。这使得我们可以手动处理每一批返回的结果而无需一次性加载全部数据。
代码示例如下:
```java
import com.baomidou.mybatisplus.core.handlers.ResultHandler;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
public class TaskService extends ServiceImpl<TaskMapper, Task> {
public void processLargeDataset(Long projectId, Long taskId) {
QueryWrapper<TaskResultInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(TaskResultInfo::getProjectId, projectId)
.eq(TaskResultInfo::getTaskId, taskId);
baseMapper.selectList(queryWrapper).forEachRemaining(task -> {
// 对每一项执行业务逻辑
handleTask(task);
});
}
private void handleTask(Task task) {
// 具体处理逻辑
}
}
```
注意:上述代码片段仅适用于简单场景下的逐一处理。如果需要更高效的批量处理能力,则应结合 ResultHandler 实现自定义逻辑。
##### 3. 结合 ResultHandler 批量处理
对于超大规模数据集,推荐使用 ResultHandler 来进一步优化性能。以下是一个完整的例子:
```java
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
public void readInStream(SqlSession sqlSession, Long projectId, Long taskId) {
Map<String, Object> params = new HashMap<>();
params.put("projectId", projectId);
params.put("taskId", taskId);
sqlSession.select("listTaskResultIpInfo", params, new ResultHandler<Object>() {
@Override
public void handleResult(ResultContext<? extends Object> context) {
String info = (String) context.getResultObject();
// 对每一条记录进行单独处理
processData(info);
}
});
}
private void processData(String data) {
// 数据处理逻辑
}
```
在此案例中,每当有一条新纪录被读取出来时都会立即调用 `handleResult` 方法对其进行加工转换,而不是等待整张表扫描完毕之后才开始行动[^2]。
---
#### 三、注意事项
1. **ResultSetType**
默认情况下,JDBC 使用的是 `FORWARD_ONLY` 类型的结果集,这种类型的优点在于它不会缓存任何历史数据,因此非常适合用于只读操作。然而,如果你确实需要回滚访问先前已经遍历过的部分,请改用 `SCROLL_INSENSITIVE` 或者 `SCROLL_SENSITIVE` 模式,但这会相应增加额外的内存负担[^1]。
2. **Fetch Size 设置**
合理调整 `fetchSize` 值至关重要。过小的数值虽然减少了单次传输的数据量但也增加了网络交互次数;反之则可能重新引入内存问题。一般建议依据硬件条件测试得出最佳平衡点。
3. **事务管理**
在长时间运行的大规模查询过程中保持开启状态的事务可能导致锁资源耗尽或者其他并发冲突情况发生。所以除非必要否则尽量关闭自动提交并将每个独立的小单元封装成短生命周期内的子交易来完成各自的任务。
---
#### 四、总结
综上所述,借助 MyBatis-Plus 的流式读取特性配合恰当配置后的 JDBC 参数组合拳出击,完全可以妥善化解因海量数据引起的潜在 OOM 危险信号灯闪烁局面!
---
###
阅读全文
相关推荐














