探索内容

MySQL JDBC StreamResult通信原理浅析 一文中,作者阐述了MySQL的流式查询实则是客户端行为,但具体究竟是JDBC哪里的行为并未说明,只举例了现象,好奇心驱使下断点了源码,发现确实如上文作者所述是客户端行为,记录一些浅薄的见解。

问题

先下现象

  • Xmx8g的默认情况下查询500w数据大概消耗4.9g老年代内存,执行时间55s左右
  • Xmx3g的默认情况下查询500w数据会出现OOM情况
  • Xmx3g + Cursor

将问题统一抛出

  • 在SpringBoot+Mybatis+Mysql的项目中,查询大量数据的时候如何防止一次性从数据库获取的数据过多导致内存溢出
使用mybatis的Cursor搭配@Transaction
  • 为什么单独使用Cursor有内存下降效果,但是却没有达到流式查询
单独使用Cursor,不用
  • 为什么配合useCursorFetch使用Cursor查询的时候,内存仍然占用会比较高
需要添加useCursorFetch=true

内存下降推断是由于JDBC实现类走了BinaryResultsetReader,而默认情况是走TextResultsetReader,前者使用了二进制封装数据
  • Cursor和Streaming的区别
Cursor又叫游标查询,类似JDBC与mysql交互时内部封装的一个分页操作,每次从数据库拉取一定的数据,拉取多次直至拉完数据
  • 非流式查询JDBC是怎么与Mysql进行交互以及读取到数据的?

  • 流式查询JDBC是怎么与Mysql进行交互以及读取到数据的?

  • 为什么流式查询JDBC会减少内存占用?

JDBC中三个不同的接收数据关键类

ResultsetRows接口

下面三个实现类中的next方法实现了如何与数据交互

  • ResultsetRowsStatic
  • ResultsetRowsCursor
  • ResultsetRowsStreaming

ResultsetRowsStatic

Represents an in-memory result set 
代表内存中的结果集
通常情况下的JDBC结果集

ResultsetRowsCursor

Model for result set data backed by a cursor 
(see http:// dev. mysql. com/ doc/ refman/ 5.7/ en/ cursors. html and SERVER_STATUS_CURSOR_EXISTS flag description on http:// dev. mysql. com/ doc/ internals/ en/ status-flags. html). 
Only works for forward-only result sets (but still works with updatable concurrency).

游标支持的结果集数据模型
(请参阅 http:// dev. mysql. com/ doc/ refman/ 5.7/ en/ cursors. html 和 http:// dev. mysql. com/ doc/ internals/ en/ status-flags. html 上的 SERVER_STATUS_CURSOR_EXISTS 标志说明)。
仅适用于前向结果集(但仍适用于可更新并发性)。

在使用了useCursor=true搭配fetchSize时的JDBC结果集封装类

ResultsetRowsStreaming

Provides streaming of Resultset rows. Each next row is consumed from the input stream only on next() call. Consumed rows are not cached thus we only stream result sets when they are forward-only, read-only, and the fetch size has been set to Integer. MIN_VALUE (rows are read one by one).

流的方式封装结果集,调用next的时候消耗下一行记录,消耗的行记录不会被缓存,默认参数情况下需要添加fetchSize=MIN_VALUE

备忘
1.在使用useCursorFetch=true参数的时候,JDBC会自动将useServerPrepStmts设置为true,此时会导致ClientPreparedStatement实现类实际为ServerPreparedStatement
源码的位置如下

package com.mysql.cj.jdbc;

public class JdbcPropertySetImpl extends DefaultPropertySet implements JdbcPropertySet {
    
    if (getBooleanProperty(PropertyKey.useCursorFetch).getValue()) {
        // assume server-side prepared statements are wanted because they're required for this functionality
        super.<Boolean>getProperty(PropertyKey.useServerPrepStmts).setValue(true);
    }
    
}

未完待续

参考链接:
MySQL JDBC StreamResult通信原理浅析_enablestreamingresults-CSDN博客