MySQL JDBC/MyBatis Stream方式读取SELECT超大结果集
情景: 遍历并处理一个大表中的所有数据, 这个表中的数据可能会是千万条或者上亿条, 很多人可能会说用分页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<XXXObject>, 这样问题就更明显了, 要将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 id="findAllRoles" fetchSize="-2147483648" resultType="chenlong.mybatislearn.db.struct.Role">
SELECT * FROM role
</select>
但还有一个问题就是这种方式必须自己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中位置为:
<property name="url" value="jdbc:mysql://localhost:3008/mybatislearn?autoReconnect=true&useCursorFetch=true"/>
实测这种Server Side Cursor执行sql后要等很久才开始返回结果, 而Client Side Cursor几乎是瞬间就开始返回结果; 网上查询后的结果是Server Side Cursor使用MySQL Server端的资源(内存/CPU……)处理Cursor, 这个可能是其原因, 但一旦开始返回结果目测两者差别不大.
两者各有优缺点, 尤其是Client Side Cursor必须自己记得ResultSet.close()否则整个连接将不再可用, 此为大坑, 尤其是有连接池的情况.
再次也发现MySQL相比其他大型RDBMS的弱点, 这种查询游标遍历本该是标配! 而MySQL用这么LOW的实现, 还需要用户掌握这么多黑魔法……F***
参考代码如下:
http://files.cnblogs.com/files/logicbaby/MyBatisLearn.zip
MySQL JDBC/MyBatis Stream方式读取SELECT超大结果集的更多相关文章
- java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决方式
昨天整理桌面的时候将桌面的一堆文件移动到F盘去了,结果导致原来建的一些项目名称所有出现红色感叹号,打开一看,原来是由于我把hibernate的那些jar包移走了.导致user library里那些ja ...
- MySQL实战 | 01-当执行一条 select 语句时,MySQL 到底做了啥?
原文链接:当执行一条 select 语句时,MySQL 到底做了啥? 也许,你也跟我一样,在遇到数据库问题时,总时茫然失措,想重启解决问题,又怕导致数据丢失,更怕重启失败,影响业务. 就算重启成功了, ...
- mysql数据库插入数据获取自增主键的三种方式(jdbc PreparedStatement方式、mybatis useGeneratedKeys方式、mybatis selectKey方式)
通常来说对于mysql数据库插入数据获取主键的方法是采用selectKey的方式,特别是当你持久层使用mybatis框架的时候. 本文除此之外介绍其它两种获取主键的方式. 为了方便描述我们先建一张my ...
- 从零开始学JAVA(09)-使用SpringMVC4 + Mybatis + MySql 例子(注解方式开发)
项目需要,继续学习springmvc,这里加入Mybatis对数据库的访问,并写下一个简单的例子便于以后学习,希望对看的人有帮助.上一篇被移出博客主页,这一篇努力排版整齐,更原创,希望不要再被移出主页 ...
- SparkStreaming直连方式读取kafka数据,使用MySQL保存偏移量
SparkStreaming直连方式读取kafka数据,使用MySQL保存偏移量 1. ScalikeJDBC 2.配置文件 3.导入依赖的jar包 4.源码测试 通过MySQL保存kafka的偏移量 ...
- mybatis&plus系列------Mysql的JSON字段的读取和转换
mybatis&plus系列------Mysql的JSON字段的读取和转换 一. 背景 在平常的开发中,我们可能会有这样的需求: 业务数据在存储的时候,并不是以mysql中的varchar丶 ...
- mysql jdbc性能优化之mybatis/callablestatement调用存储过程mysql jdbc产生不必要的元数据查询(已解决,cpu负载减少20%)
INFO | jvm 1 | 2016/08/25 15:17:01 | 16-08-25 15:17:01 DEBUG pool-1-thread-371dao.ITaskDao.callProce ...
- jdbc -- 001 -- 一般方式创建数据库连接(oracle/mysql)
连接数据库步骤: 1. 注册驱动(只做一次) 2. 建立连接(Connection) 3. 创建执行SQL的语句(Statement) 4. 执行语句 5. 处理执行结果(ResultSet) 6. ...
- JDBC纯驱动方式连接MySQL
1 新建一个名为MysqlDemo的JavaProject 2 从http://dev.mysql.com/downloads/connector/j/中下载最新的驱动包. 这里有.tar.gz和.z ...
随机推荐
- 50个常用SQL语句
50个常用SQL语句 Student(S#,Sname,Sage,Ssex) 学生表 S#学号,主键 Course(C#,Cname,T#) 课程表 C#课程号,主键 SC(S#, ...
- 简单谈谈eclipse下搭建PhoneGap环境来开发Android程序 - linux86(转)
原来在逛园子的时候一不小心发现了一个新概念“PhoneGap”简称PG,我一直都喜欢追逐新事物,自然就产生了好奇心.于是乎我就在百度上面Google了一下PhoneGap是什么东西.简单的说就是用另一 ...
- Ant打包
<?xml version="1.0" encoding="UTF-8"?> <!--打包的实质生成jar包的实质1.编译工程src文件夹下的 ...
- winform label文本转换为图片 、Picturebox+label合并转换为图片
public Form1() { InitializeComponent(); //label存入Picturebox pictureBox1.Controls.Add(label1); pictur ...
- [TCPIP] DNS Note
TCPIP DNS 域名系统 DNS 是一个应用于TCP/IP应用程序的分布式数据库,它提供主机名字和IP地址之间的转换及有关电子邮件的选路信息. 对DNS的访问是通过一个地址解析器来完成的,在Un ...
- ISPA
来自CSDN的Rachel Zhang 4. Improved SAP 算法 本次介绍的重头戏.通常的 SAP 类算法在寻找增广路时总要先进行 BFS,BFS 的最坏情况下复杂度为 O(E),这样使得 ...
- python 中的decorator
python 中decorator的作用就是一个包装的作用,所谓包装指在执行真正的函数之前或者之后,我们可以有一些额外的发挥余地. decorator形式如下 def dec(arg1): print ...
- ImageLoader图片加载
http://blog.csdn.net/liu1164316159/article/details/38728259 转载请注明http://write.blog.csdn.net/po ...
- 在MAC服务器上搭建docker-registry(转)
在MAC服务器上搭建docker-registry 2014-03-14 0个评论 来源:在MAC服务器上搭建docker-registry 收藏 我要投稿 本文介绍如何在M ...
- IOS的H5页面滑动不流畅的问题:
IOS的H5页面滑动不流畅的问题: -webkit-overflow-scrolling : touch; 需要滑动的是哪块区域,就在哪里加上这段代码就OK