基于 mysql 8.0
基础介绍
com.mysql.cj.protocol.ResultsetRows
该接口表示的是应用层如何访问 db 返回回来的结果集
它有三个实现类
ResultsetRowsStatic
默认实现。连接 db 的 url 没有增加额外的参数、单纯就是 ip port schema 。
@Test
public void generalQuery() throws Exception {
String sql = "select * from test";
ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery(sql);
int count = 0;
while (rs.next()) {
count++;
}
System.out.println(count);
}
那么这个时候、你的 ResultSet 对应的实现类里面的成员变量
com.mysql.cj.protocol.a.result.NativeResultset#rowData
的实现类就是 ResultsetRowsStatic
这个时候是最蠢的模式、因为 mysql 的驱动层只有接收完 db 所有数据才会返回到应用层。对应代码就是 ResultSet rs = ps.executeQuery(sql);
应用到线程会卡住在这里。直到驱动层返回。
假如表 test 中有 100w 数据、那么发生 OOM 到地方将会是 mysql 驱动层内部的代码、因为它自己将数据存起
// 来源 com.mysql.cj.protocol.a.BinaryResultsetReader#read
BinaryRowFactory brf = new BinaryRowFactory(this.protocol, cdef, resultSetFactory.getResultSetConcurrency(), false);
ArrayList<ResultsetRow> rowList = new ArrayList<>();
// 真正获取数据
ResultsetRow row = this.protocol.read(ResultsetRow.class, brf);
while (row != null) {
if ((maxRows == -1) || (rowList.size() < maxRows)) {
rowList.add(row);
}
row = this.protocol.read(ResultsetRow.class, brf);
}
rows = new ResultsetRowsStatic(rowList, cdef);
// =============== 分割 ==========
private List<Row> rows;
@SuppressWarnings("unchecked")
public ResultsetRowsStatic(List<? extends Row> rows, ColumnDefinition columnDefinition) {
this.currentPositionInFetchedRows = -1;
this.rows = (List<Row>) rows;
this.metadata = columnDefinition;
}
ResultsetRowsCursor
想要启用这种模式、需要在连接 db 的 url 中加上参数 useCursorFetch=true
jdbc:mysql://127.0.0.1:3306/test-db?useCursorFetch=true
并且对应的 fetchSize 要大于 0
我们看下源码 com.mysql.cj.jdbc.ConnectionImpl#prepareStatement(java.lang.String)
发现参数 int resultSetType, int resultSetConcurrency
默认值为
private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;
private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
当我们设置了 useCursorFetch=true
之后、useServerPrepStmts
会被设置为 true
在 com.mysql.cj.jdbc.ConnectionImpl#ConnectionImpl(com.mysql.cj.conf.HostInfo)
中 com.mysql.cj.conf.PropertySet#initializeProperties
com.mysql.cj.jdbc.JdbcPropertySetImpl#postInitialization
if (getBooleanProperty(PropertyKey.useCursorFetch).getValue()) {
// assume server-side prepared statements are wanted because they're required for this functionality
super.<Boolean>getProperty(PropertyKey.useServerPrepStmts)