流式查询
流式查询 指的是查询成功后不是返回一个集合而是返回一个迭代器,应用可以通过迭代器每次取一条查询结果。流式查询的好处是能够降低内存使用。
例如我们想要从数据库取 1000 万条记录而又没有足够的内存时,就不得不分页查询。而分页查询就需要我们按照顺序查询并设置一个参数来记录当前进度并在下次查询时将进度作为参数传入(比如按id升序查询,记录每次查询结果的最大id, 下次查询将这个最大id传入只查询大于这个id的),否则就会出现深度分页的情况。而查询效率取决于表设计,如果设计的不好,那么每次查询都会是一次单独的低效查询。
而流式查询不需要自己记录进度(数据库来记录),且即使表设计的较差或在sql比较复杂,也仅仅只需要一次低效查询。
流式查询的过程当中,要保证数据库连接是保持打开状态的,否则就会流关闭。
MyBaits通过游标Cursor实现了流式查询。 MyBaits Plus基于Mybais, 自然也是支持的。
如何使用
写一个获取流的Mapper
不需要其他配置, 像平常我们写查询一样在Mapper定义查询,并将返回结果设为Cursor即可实现一个流式查询。
sql也正常按照时间查询条件写,不需要加limit之类的参数。
java
Cursor<MyDbTableDo> queryByCursor()
获取一个打开状态的连接。
上面提过,使用流式查询需要保证数据库连接是保持打开状态。而正常情况下我们使用mybaits执行一次查询,连接都会被关闭或在重置。因此我们需要一些方法来保持连接。
使用事务
事务执行完毕之前连接会一直保持因此,我们可以来使用事务来保持连接。
@Transactional 注解
java
@Transactional
public void transactionalAnno() throws Exception {
try (Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor()) {
cursor.forEach(item -> { });
}
}
这是最简单的方法,但是需要注意的是,由于cursor在遍历结束的方法末尾后会主动关闭连接。因此:
- 方法内事务是正常的,在cursor查询前和遍历后的的数据操作依然是一个事务。从这里可以猜测,连接并不在在遍历完立即关闭,而是采用了类似AOP的手段在方法末尾关闭。
- 如果你使用了数据库连接池,那么池中的这个连接会被关掉,这可能并不会导致你的连接池数量减少,因为连接池的连接数据依赖于各自的规则,当有连接断开,连接池会根据自己的策略对做相应的处理,比如重新建立,因此不需要过分关注这个,需要注意的是这个行为可能带来的影响,比如druid连接池,druid在事务执行完毕后会进行连接的清理。但是这个连接在myDbTableMapper遍历完成时已经关闭,就会导致日志打印java.sql.SQLException: No operations allowed after statement closed。这个问题不会带来数据影响,但是依然属于一个错误,见github-druid-issues,在1.2.10, druid将这个日志改为debug级别进行了临时的屏蔽。其他连接池尚未进行测试。
TransactionTemplate开启事务
java
public void transactionTemplate() throws Exception {
TransactionTemplate transactionTemplate =
new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
try (Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor()) {
cursor.forEach(item -> { });
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
}
在 Spring 中,用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的
SqlSessionFactory主动打开一个连接
SqlSessionFactory 来手工打开数据库连接,SqlSessionFactory直接注入即可,MyBatis维护了这个Bean.
java
public void sqlSessionFactory() throws Exception {
try (
SqlSession sqlSession = sqlSessionFactory.openSession();
Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor();
) {
cursor.forEach(item -> { });
}
}
开启一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;然后使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的。
循环获取数据
Cursor继承了迭代器,可以通过Cursor获取他的Iterator, 或者直接使用for循环来获取数据。
需要注意的是,Cursor在查询一瞬间数据就固化了,如果你先查询,再更新,再遍历,即使在同一个事务里面,获得的结果也是未更新的数据。
关闭流
cursor在遍历结束后会主动关闭连接。如果未遍历结束中途退出,可以调用cursor的close方法关闭连接。推荐finaly内总是调用close或者try(resoure)来保证连接总是被正常关闭。
参考资料:
你还在用分页?试试 MyBatis 流式查询,真心强大!
关联导航:MySQL架构基础
关联导航:MySQL数据类型选择与设计
关联导航:创建高性能的索引
关联导航:查询性能优化
关联导航:EXPLAIN的使用
关联导航:深度了解MySql的InnoDB引擎
欢迎关注...