情景: 遍历并处理一个大表中的所有数据, 这个表中的数据可能会是千万条或者上亿条, 很多人可能会说用分页limit……但需求本身一次性遍历更加方便, 且Oracle/DB2都有方便的游标机制.

对DB来说Stream其实也就是我们说的游标(Cursor), MySQL的Stream方式有2种, Client Side Cursor和Server Side Cursor. JDBC默认的方式Client Side Cursor, 没有任何设置的默认情况下JDBC驱动会将select的全部结果都读取到Client Side后再处理, 这样的话当select返回的结果集非常大时将会撑爆Client端的内存, JDBC下就是普通的OOM; 当然用MyBatis之类的ORM也有同样的问题, 因为这些东西都是架构在JDBC之上的.

解决办法:

1. 使用Client Side Cursor

PreparedStatement/Statement的setFetchSize方法设置为Integer.MIN_VALUE或者使用方法Statement.enableStreamingResults(), 其实这个方法和设置Integer.MIN_VALUE一样, 源码如下:

public void enableStreamingResults() throws SQLException {

synchronized (checkClosed().getConnectionMutex()) {

this.originalResultSetType = this.resultSetType;

this.originalFetchSize = this.fetchSize;

setFetchSize(Integer.MIN_VALUE);

setResultSetType(ResultSet.TYPE_FORWARD_ONLY);

}

}

在网上查了下这种Client Side Cursor的大概实现, 其实mysql本身并没有FetchSize方法, 它是通过使用CS阻塞方式的网络流控制实现服务端不会一下发送大量数据到客户端撑爆客户端内存, 我认为这种方式非常LOW! 是很明显的"补丁"策略; 这样就会造成一个必然的问题就是如果没有全部读取完ResultSet的结果再执行其他sql, 那么将会影响该连接的缓存, 所以这种方式要求要么读取完ResultSet中的全部数据要么需要自己调用ResultSet.close()方法, 也就是得用try {} finally{ rs.close(); }或者jdk7下的try-with-resources语法, 例如:

try (ResultSet rs = pstmt.executeQuery()) {

Role role = new Role();

int i = 0;

while (rs.next()) {

try {

role.setRoleId(rs.getString("roleId"));

role.setState(rs.getInt("state"));

role.setMiscData(rs.getString("miscData"));

selectHandler.action(role);

} catch (Exception ex) {

logger.error("selectAllRoles error!", ex);

}

}

}

常用的ORM MyBatis下, 默认select的结果是一个List, 这样问题就更明显了, 要将select全部结果放到一个集合中再处理, 那么结果集一大OOM是必然; 经过查询MyBatis资料发现有ResultHandler机制, 就是这样handler:

sqlSession.select("chenlong.mybatislearn.db.mapper.RoleMapper.findAllRoles", handler);

但是和JDBC方式一样, MyBatis即便用了ResultHandler也是将所有结果都读到Client Side, 内存一样爆掉, 最后总算发现xml mapper里可以配置select的fetchSize, 按照前面JDBC方式将其配置为Integer.MIN_VALUE即-2147483648就正常了, 如下:

SELECT * FROM role

但还有一个问题就是这种方式必须自己ResultSet.close(), 通过扒MyBatis代码发现它已经帮我们做了, 如下

这样就可以放心的在MyBatis下使用Client Side Cursor了.

2. 使用Server Side Cursor

MySQL JDBC Driver文档中有这样参数的说明:

useCursorFetch

If connected to MySQL > 5.0.2, and setFetchSize() > 0 on a statement, should that statement use cursor-based fetching to retrieve rows?

Default: false

Since version: 5.0.0

在MyBatis中位置为:

实测这种Server Side Cursor执行sql后要等很久才开始返回结果, 而Client Side Cursor几乎是瞬间就开始返回结果; 网上查询后的结果是Server Side Cursor使用MySQL Server端的资源(内存/CPU……)处理Cursor, 这个可能是其原因, 但一旦开始返回结果目测两者差别不大.

两者各有优缺点, 尤其是Client Side Cursor必须自己记得ResultSet.close()否则整个连接将不再可用, 此为大坑, 尤其是有连接池的情况.

再次也发现MySQL相比其他大型RDBMS的弱点, 这种查询游标遍历本该是标配! 而MySQL用这么LOW的实现, 还需要用户掌握这么多黑魔法……F***

参考代码如下:

mybatis mysql cursor_MySQL JDBC/MyBatis Stream方式读取SELECT超大结果集相关推荐

  1. mybatis mysql schema_学习Mybatis与mysql数据库的示例笔记

    目录结构: pom.xml文件 1 <?xml version="1.0" encoding="UTF-8"?> 2 3 xmlns:xsi=&qu ...

  2. golang mysql大量写入_Golang 实现分片读取http超大文件流和并发控制

    分片读取http超大文件流 Golang中的HTTP发送get请求,在获取内容有两种情况. Golang发送http get请求方式 resp, err := http.Get(sendUrl) if ...

  3. springboot+mybatis+mybatis +mysql读写分离(AOP方式)

    引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式. 第一种是依靠中间 ...

  4. mybatis mysql merge_使用Mybatis和druid连接池报错 merge sql error, dbType mysql, sql

    在项目中用mybatis查询的时候遇到一个这样的问题,抛错详情如下: merge sql error, dbType mysql, sql : select r.id from role r inne ...

  5. mybatis mysql merge_Spring Boot + Mybatis 整合Mysql ,SQLServer数据源以及整合druid,动态调整数据源切换。...

    pom.xml依赖 org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-bo ...

  6. jdbc mysql user_tab_comments_mysql/jdbc:设置useInformationSchema=true读取表注释信息(table_comment)...

    问题描述 今天在读取表的注释信息(COMMENT)时,发现返回的REMARKS字段返回居然是null. 以下是代码示例: DatabaseMetaData meta = this.pConnectio ...

  7. mybatis mysql concat_在MyBatis中使用concat()方法

    concat介绍 CONCAT(字串1, 字串2, 字串3, ...): 将字串1.字串2.字串3,等字串连在一起. 示例 SELECT CONCAT(region_name,store_name) ...

  8. 基于JAVA+SpringBoot+Mybatis+MYSQL的高铁售票系统

    高铁售票系统 高铁售票系统是一个简单的Spring Boot实战项目,提供铁路运行信息查询以及购票出行服务(模拟). 项目用到的技术 SpringBoot2.1.6 Mybatis Mysql Red ...

  9. mybatis 配置 mysql连接池_spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)...

    项目目录结构 1. 导入依赖 创建 maven 工程,除了 Spring 的基本依赖外,还需要导入 Mybatis 和 Druid 的相关依赖: org.springframeworkgroupId& ...

最新文章

  1. IDA Pro 修改默认名称
  2. Redis进阶 - Redis主从工作原理详解
  3. python登录代码思路_终于找到一个思路比较清晰的可以模拟登录百度的代码!
  4. JavaScript强化教程 —— JavaScript 总结
  5. LeetCode 98. 验证二叉搜索树(中序遍历)
  6. MaxCompute 实现增量数据推送(全量比对增量逻辑)
  7. android启动其他app的服务器,Android中通过外部程序启动App的三种方法
  8. 政策解读:《智能硬件产业创新发展专项行动(2016-2018年)》(上)
  9. 由于找不到Qt5widgets.dll,无法继续执行代码。重新安装程序可能会解决此问题。
  10. 【iOS】TouchDown、TouchDownRepeat 和 TouchCancel 的区别
  11. python怎么用for循环找出最大值_从“for in”循环中获取最小值和最大值
  12. tensorflow——deeplabv3+训练cityscapes数据集(未完待续)
  13. csgo手机上看demo_《CSGO》观看游戏DEMO方法 怎么观看游戏DEMO
  14. Microsoft Excel 教程:如何在 Excel 中隐藏或显示行或列?
  15. 薛定谔的猫、量子纠缠、和量子计算机
  16. 没项目实战经验?分享自学练手的软件测试项目实战+数据库+接口,部署超级简单
  17. 日常瘦脸8个关键细节
  18. python打开文件管理器
  19. W: 校验数字签名时出错。此仓库未被更新,所以仍然使用此前的索引文件。GPG 错误:http://mirrors.ustc.edu.cn/kali kali-rolling
  20. Edge浏览器安装油猴插件以及好用的插件推荐

热门文章

  1. 使用Oracle创建图书馆数据库(book reader lib loan表)
  2. Git的使用---版本控制
  3. 第三章--数据链路层
  4. 语义分割--Dilated Residual Networks 之转载
  5. 使用python手写FFT算法
  6. python中tkinter圆弧_Tkinter(Python)中弧的选项
  7. linux本地监听创建,linux 创建监听服务器
  8. 简单html图片轮播_web前端入门到实战:简单的图片轮播
  9. matlab系统的根轨迹,实验五 利用MATLAB绘制系统根轨迹
  10. 基于注解的 AOP 配置