前言
本章分析ShardingJDBC的核心步骤:结果合并。
一、QueryResult
public interface QueryResult {
boolean next() throws SQLException;
Object getValue(int columnIndex, Class<?> type) throws SQLException;
Object getCalendarValue(int columnIndex, Class<?> type, Calendar calendar) throws SQLException;
InputStream getInputStream(int columnIndex, String type) throws SQLException;
boolean wasNull() throws SQLException;
int getColumnCount() throws SQLException;
String getColumnName(int columnIndex) throws SQLException;
String getColumnLabel(int columnIndex) throws SQLException;
}
QueryResult是对java.sql.ResultSet的封装,提供了和ResultSet一样的next方法,其他方法看名字也很容易理解。
重点看一下QueryResult的两个实现。
1、MemoryQueryResult
MemoryQueryResult对应CONNECTION_STRICTLY连接限制模式。当sql数量大于单次查询最大连接数限制(max.connections.size.per.query默认1)启用。采用内存归并,一次性读取ResultSet结果集放入内存,防止使用过多数据库连接。
public final class MemoryQueryResult implements QueryResult {
// java.sql.ResultSetMetaData ResultSet元数据信息
private final ResultSetMetaData resultSetMetaData;
// 一次性读取ResultSet结果集的行集合
private final Iterator<List<Object>> rows;
// 当前行
private List<Object> currentRow;
}
MemoryQueryResult构造时,将ResultSet结果集全部读入内存,放入rows。
public MemoryQueryResult(final ResultSet resultSet) throws SQLException {
resultSetMetaData = resultSet.getMetaData();
rows = getRows(resultSet);
}
private Iterator<List<Object>> getRows(final ResultSet resultSet) throws SQLException {
Collection<List<Object>> result = new LinkedList<>();
while (resultSet.next()) {
List<Object> rowData = new ArrayList<>(resultSet.getMetaData().getColumnCount());
for (int columnIndex = 1; columnIndex <= resultSet.getMetaData().getColumnCount(); columnIndex++) {
Object rowValue = getRowValue(resultSet, columnIndex);
rowData.add(resultSet.wasNull() ? null : rowValue);
}
result.add(rowData);
}
return result.iterator();
}
一般使用ResultSet都是用next方法和getXXX方法,对应QueryResult是next方法和getValue方法。
private final Iterator<List<Object>> rows;
private List<Object> currentRow;
@Override
public boolean next() {
// 如果还有数据,移动currentRow至下一行,并返回true
if (rows.hasNext()) {
currentRow = rows.next();
return true;
}
// 没数据返回false
currentRow = null;
return false;
}
@Override
public Object getValue(final int columnIndex, final Class<?> type) {
// 获取当前行的columnIndex-1下标对应的字段值
return currentRow.get(columnIndex - 1);
}
2、StreamQueryResult
StreamQueryResult对应MEMORY_STRICTLY内存限制模式。当sql数量小于等于单次查询最大连接数限制(max.connections.size.per.query默认1)启用。采用流式归并,移动ResultSet指针获取数据,减少因为一次性读取结果集造成过多的内存开销。
public final class StreamQueryResult implements QueryResult {
// java.sql.ResultSetMetaData
private final ResultSetMetaData resultSetMetaData;
// java.sql.ResultSet
private final ResultSet resultSet;
public StreamQueryResult(final ResultSet resultSet) throws SQLException {
resultSetMetaData = resultSet.getMetaData();
this.resultSet = resultSet;
}
}
StreamQueryResult简单封装了ResultSet,基本所有QueryResult接口方法的实现,都是直接委托给ResultSet。
@Override
public boolean next() throws SQLException {
return resultSet.next();
}
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
if (boolean.class == type) {
return resultSet.getBoolean(columnIndex);
} else if (byte.class == type) {
return resultSet.getByte(columnIndex);
}
// ...
}
二、根据ConnectionMode选择QueryResult实现
上一章讲过,ConnectionMode是用于选择QueryResult实现的。
如果maxConnectionsSizePerQuery(单次查询最大连接数)< sql数量,使用CONNECTION_STRICTLY连接限制。采用内存归并,一次性读取ResultSet数据到内存,减少数据库连接开销。 如果maxConnectionsSizePerQuery(单次查询最大连接数)>= sql数量,使用MEMORY_STRICTLY内存限制。采用流式归并,ResultSet移动游标读取数据到内存,减少内存开销。
但是其实也不然,如果用户调用ResultSet resultSet = statement.executeQuery()执行sql,是根据ConnectionMode选择QueryResult实现,这是没问题的。具体见PreparedStatementExecutor#executeQuery。
public List<QueryResult> executeQuery() throws SQLException {
final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
// callback
SQLExecuteCallback<QueryResult> executeCallback = new SQLExecuteCallback<QueryResult>(getDatabaseType(), isExceptionThrown) {
@Override
protected QueryResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode) throws SQLException {
return getQueryResult(statement, connectionMode);
}
};
return executeCallback(executeCallback);
}
private QueryResult getQueryResult(final Statement statement, final ConnectionMode connectionMode) throws SQLException {
PreparedStatement preparedStatement = (PreparedStatement) statement;
ResultSet resultSet = preparedStatement.executeQuery();
getResultSets().add(resultSet);
// 根据ConnectionMode选择QueryResult实现
return ConnectionMode.MEMORY_STRICTLY == connectionMode ? new StreamQueryResult(resultSet) : new MemoryQueryResult(resultSet);
}
如果用户调用的是statement.getResultSet,像下面这样。
statement.execute();
ResultSet resultSet = statement.getResultSet();
QueryResult实现会选择流式归并StreamQueryResult,代码见ShardingPreparedStatement#getResultSet。
private ExecutionContext executionContext;
// ShardingResultSet
private ResultSet currentResultSet;
@Override
public ResultSet getResultSet() throws SQLException {
if (null != currentResultSet) {
return currentResultSet;
}
if (executionContext.getSqlStatementContext() instanceof SelectStatementContext || executionContext.getSqlStatementContext().getSqlStatement() instanceof DALStatement) {
// 对每个statement执行getResultSet得到结果集
List<ResultSet> resultSets = getResultSets();
// 将ResultSet都转换为QueryResult
List<QueryResult> queryResults = getQueryResults(resultSets);
// 执行MergeEngine
MergedResult mergedResult = mergeQuery(queryResults);
// 生成ShardingResultSet返回
currentResultSet = new ShardingResultSet(resultSets, mergedResult, this, executionContext);
}
return currentResultSet;
}
getQueryResults方法,直接new了StreamQueryResult。
private List<QueryResult> getQueryResults(final List<ResultSet> resultSets) throws SQLException {
List<QueryResult> result = new ArrayList<>(resultSets.size());
for (ResultSet each : resultSets) {
if (null != each) {
result.add(new StreamQueryResult(each));
}
}
return result;
}
三、MergedResult
结果归并最终都会交由MergeEngine处理,入参是QueryResult集合,出参是MergedResult。MergedResult的方法与QueryResult大多都类似,只是MergedResult代表的是合并之后的结果。
public interface MergedResult {
boolean next() throws SQLException;
Object getValue(int columnIndex, Class<?> type) throws SQLException;
Object getCalendarValue(int columnIndex, Class<?> type, Calendar calendar) throws SQLException;
InputStream getInputStream(int columnIndex, String type) throws SQLException;
boolean wasNull() throws SQLException;
}
MergedResult多种多样,这里看四个有代表性的抽象实现。
1、DecoratorMergedResult
@RequiredArgsConstructor
@Getter
public abstract class DecoratorMergedResult implements MergedResult {
// 合并结果
private final MergedResult mergedResult;
@Override
public final Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
return mergedResult.getValue(columnIndex, type);
}
@Override
public final Object getCalendarValue(final int columnIndex, final Class<?> type, final Calendar calendar) throws SQLException {
return mergedResult.getCalendarValue(columnIndex, type, calendar);
}
@Override
public final InputStream getInputStream(final int columnIndex, final String type) throws SQLException {
return mergedResult.getInputStream(columnIndex, type);
}
@Override
public final boolean wasNull() throws SQLException {
return mergedResult.wasNull();
}
}
DecoratorMergedResult的想法,类似笛卡尔积路由引擎ShardingCartesianRoutingEngine。它只会在有合并结果之后被构造使用,用于装饰某个MergedResult。默认用上次合并结果实现了四个方法,子类需要实现关键的next方法。
DecoratorMergedResult有三个实现类,都与分页有关,这里看MySQL的实现LimitDecoratorMergedResult。
public final class LimitDecoratorMergedResult extends DecoratorMergedResult {
// 分页上下文 从 SelectStatementContext中获取的
private final PaginationContext pagination;
// 是否跳过所有结果
private final boolean skipAll;
// 当前行数
private int rowNumber;
// 构造
public LimitDecoratorMergedResult(final MergedResult mergedResult, final PaginationContext pagination) throws SQLException {
super(mergedResult);
this.pagination = pagination;
// 计算是否需要跳过所有行
skipAll = skipOffset();
}
private boolean skipOffset() throws SQLException {
// 如果结果集中的数据量,不足offset,返回false
for (int i = 0; i < pagination.getActualOffset(); i++) {
if (!getMergedResult().next()) {
return true;
}
}
// 否则返回true,当前行设置为0
rowNumber = 0;
return false;
}
@Override
public boolean next() throws SQLException {
// 如果结果集中的数据量不足offset,直接返回false
if (skipAll) {
return false;
}
// 如果rowCount不存在,返回装饰的MergedResult.next
if (!pagination.getActualRowCount().isPresent()) {
return getMergedResult().next();
}
// 返回 当前行小于rowCount 且 MergedResult.next
return ++rowNumber <= pagination.getActualRowCount().get() && getMergedResult().next();
}
}
总得来说,就是对于select * from t_order limit 20,10(分页查询订单第三页10条数据),如果结果集不满20条数据(不存在第三页),next方法返回false(skipAll=true);如果rowNumber小于分页行数(10条数据),返回包装MergeResult的next方法。
2、TransparentMergedResult
TransparentMergedResult把所有的MergedResult需要实现的方法都直接委托QueryResult实现了,方法名字都一样,代码就不贴了。适配器模式,类似FutureTask持有Callable实现Runnable。
3、MemoryMergedResult
MemoryMergedResult和MemoryQueryResult类似,都是将输入的数据完全读入内存的一个集合中,然后提供给外部next、get方法迭代获取元素。
public abstract class MemoryMergedResult<T extends BaseRule> implements MergedResult {
// 游标
private final Iterator<MemoryQueryResultRow> memoryResultSetRows;
// 当前行
private MemoryQueryResultRow currentResultSetRow;
// 是否为空
private boolean wasNull;
protected MemoryMergedResult(final T rule, final SchemaMetaData schemaMetaData, final SQLStatementContext sqlStatementContext, final List<QueryResult> queryResults) throws SQLException {
// 子类实现init方法构造MemoryQueryResultRow集合
List<MemoryQueryResultRow> memoryQueryResultRowList = init(rule, schemaMetaData, sqlStatementContext, queryResults);
memoryResultSetRows = memoryQueryResultRowList.iterator();
if (!memoryQueryResultRowList.isEmpty()) {
currentResultSetRow = memoryQueryResultRowList.get(0);
}
}
}
MemoryMergedResult父类通过memoryResultSetRows迭代器和memoryResultSetRows当前行可以实现next等MergedResult接口的方法。
@Override
public final boolean next() {
if (memoryResultSetRows.hasNext()) {
currentResultSetRow = memoryResultSetRows.next();
return true;
}
return false;
}
@Override
public final Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
if (Blob.class == type || Clob.class == type || Reader.class == type || InputStream.class == type || SQLXML.class == type) {
throw new SQLFeatureNotSupportedException();
}
Object result = currentResultSetRow.getCell(columnIndex);
wasNull = null == result;
return result;
}
只需要子类提供init方法的实现,返回一个MemoryQueryResultRow集合。
MemoryQueryResultRow构造时将QueryResult中的数据通过load方法放入成员变量data数组中,这也是MemoryMergedResult区别于StreamMergedResult的地方,它将数据全部读入内存中处理。
此外MemoryQueryResultRow提供了get、set方法修改data数组。
public final class MemoryQueryResultRow {
private final Object[] data;
public MemoryQueryResultRow(final QueryResult queryResult) throws SQLException {
data = load(queryResult);
}
private Object[] load(final QueryResult queryResult) throws SQLException {
int columnCount = queryResult.getColumnCount();
Object[] result = new Object[columnCount];
for (int i = 0; i < columnCount; i++) {
result[i] = queryResult.getValue(i + 1, Object.class);
}
return result;
}
public Object getCell(final int columnIndex) {
Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
return data[columnIndex - 1];
}
public void setCell(final int columnIndex, final Object value) {
Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
data[columnIndex - 1] = value;
}
}
MemoryMergedResult的实现类GroupByMemoryMergedResult处理groupby分组聚合逻辑。看看它的init方法。
@Override
protected List<MemoryQueryResultRow> init(final ShardingRule shardingRule,
final SchemaMetaData schemaMetaData,
final SQLStatementContext sqlStatementContext,
final List<QueryResult> queryResults) throws SQLException {
SelectStatementContext selectStatementContext = (SelectStatementContext) sqlStatementContext;
// 单个groupby值 - 合并结果集
Map<GroupByValue, MemoryQueryResultRow> dataMap = new HashMap<>(1024);
// 单个groupby值 - 聚合条件(比如sum、avg、count) - 聚合结果
Map<GroupByValue, Map<AggregationProjection, AggregationUnit>> aggregationMap = new HashMap<>(1024);
for (QueryResult each : queryResults) {
while (each.next()) {
// 从每行返回的数据里,取出groupby对应的字段的值,构造GroupByValue
GroupByValue groupByValue = new GroupByValue(each, selectStatementContext.getGroupByContext().getItems());
// 初始化dataMap和aggregationMap对应的第一个kv对
initForFirstGroupByValue(selectStatementContext, each, groupByValue, dataMap, aggregationMap);
// 合并 调用AggregationUnit执行合并操作,结果暂存在AggregationUnit
aggregate(selectStatementContext, each, groupByValue, aggregationMap);
}
}
// 将aggMap中AggregationUnit计算得到的数据,更新到dataMap的MemoryQueryResultRow中
setAggregationValueToMemoryRow(selectStatementContext, dataMap, aggregationMap);
// 根据表的元数据信息,判断字段是否大小写敏感(后续排序的依据)
List<Boolean> valueCaseSensitive = queryResults.isEmpty() ? Collections.emptyList() : getValueCaseSensitive(queryResults.iterator().next(), selectStatementContext, schemaMetaData);
// 将dataMap的MemoryQueryResultRow排序并返回
return getMemoryResultSetRows(selectStatementContext, dataMap, valueCaseSensitive);
}
举个例子:select user_id, date_format(created_at, '%Y-%m'), avg(paid_amount) from t_order group by user_id, date_format(created_at,'%Y-%m')统计用户每月的每单平均支付金额。(这里SelectStatementContext#isSameGroupByAndOrderByItems返回true,其实不会走GroupByMemoryMergedResult,会走GroupByStreamMergedResult,但是逻辑是一样的)
针对每个user_id和月(GroupByValue),累计单量,累计支付金额,然后用累计支付金额/累计单量即可(AggregationProjection&AggregationUnit)。
GroupByValue根据groupby字段,找出对应的groupby字段的值,作为每行数据的聚合维度。比如groupby字段是user_id,这行数据的user_id是1,那groupValues列表就是[1]。
@Getter
@EqualsAndHashCode // 重写了equals&hasCode方法 作为Map的Key
public final class GroupByValue {
// groupby的值列表 比如user_id=1,月份=2020-12
private final List<?> groupValues;
public GroupByValue(final QueryResult queryResult, final Collection<OrderByItem> groupByItems) throws SQLException {
groupValues = getGroupByValues(queryResult, groupByItems);
}
// 根据groupby字段所处select字段的下标,找到groupby的值放入groupValues
private List<?> getGroupByValues(final QueryResult queryResult, final Collection<OrderByItem> groupByItems) throws SQLException {
List<Object> result = new ArrayList<>(groupByItems.size());
for (OrderByItem each : groupByItems) {
result.add(queryResult.getValue(each.getIndex(), Object.class));
}
return result;
}
}
AggregationProjection代表一个select语句中的聚合字段,存储了别名,聚合类型,聚合函数里的表达式,衍生聚合字段(求avg平均值ProjectionsTokenGenerator会生成sum和count两个聚合查询),groupby字段对应select字段的下标位置。
@EqualsAndHashCode
@ToString
public class AggregationProjection implements Projection {
// AVG
private final AggregationType type;
// (paid_amount)
private final String innerExpression;
// null
private final String alias;
// 衍生聚合字段 SUM(paid_amount),COUNT(*)
private final List<AggregationProjection> derivedAggregationProjections = new ArrayList<>(2);
// groupby字段 对应 select字段 的 下标位置
@Setter
private int index = -1;
}
AggregationUnit负责处理不同的聚合逻辑,由工厂统一创建。
public final class AggregationUnitFactory {
public static AggregationUnit create(final AggregationType type, final boolean isDistinct) {
switch (type) {
case MAX:
return new ComparableAggregationUnit(false);
case MIN:
return new ComparableAggregationUnit(true);
case SUM:
return isDistinct ? new DistinctSumAggregationUnit() : new AccumulationAggregationUnit();
case COUNT:
return isDistinct ? new DistinctCountAggregationUnit() : new AccumulationAggregationUnit();
case AVG:
return isDistinct ? new DistinctAverageAggregationUnit() : new AverageAggregationUnit();
default:
throw new UnsupportedOperationException(type.name());
}
}
}
比如AverageAggregationUnit处理普通的avg逻辑,merge合并方法,累加count和sum;getResult方法返回sum/count。
@RequiredArgsConstructor
public final class AverageAggregationUnit implements AggregationUnit {
private BigDecimal count;
private BigDecimal sum;
@Override
public void merge(final List<Comparable<?>> values) {
if (null == values || null == values.get(0) || null == values.get(1)) {
return;
}
if (null == count) {
count = new BigDecimal("0");
}
if (null == sum) {
sum = new BigDecimal("0");
}
count = count.add(new BigDecimal(values.get(0).toString()));
sum = sum.add(new BigDecimal(values.get(1).toString()));
}
@Override
public Comparable<?> getResult() {
if (null == count || BigDecimal.ZERO.equals(count)) {
return count;
}
// TODO use metadata to fetch float number precise for database field
return sum.divide(count, 4, BigDecimal.ROUND_HALF_UP);
}
}
4、StreamMergedResult
StreamMergedResult流式归并结果,实现除了next以外其他方法,子类需要实现next方法移动游标,设置currentQueryResult(当前QueryResult)。
@Setter
public abstract class StreamMergedResult implements MergedResult {
// 当前结果集
private QueryResult currentQueryResult;
// 是否为空
private boolean wasNull;
protected final QueryResult getCurrentQueryResult() throws SQLException {
return currentQueryResult;
}
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
Object result = getCurrentQueryResult().getValue(columnIndex, type);
wasNull = getCurrentQueryResult().wasNull();
return result;
}
@Override
public Object getCalendarValue(final int columnIndex, final Class<?> type, final Calendar calendar) throws SQLException {
Object result = getCurrentQueryResult().getCalendarValue(columnIndex, type, calendar);
wasNull = getCurrentQueryResult().wasNull();
return result;
}
@Override
public final InputStream getInputStream(final int columnIndex, final String type) throws SQLException {
InputStream result = getCurrentQueryResult().getInputStream(columnIndex, type);
wasNull = getCurrentQueryResult().wasNull();
return result;
}
@Override
public final boolean wasNull() {
return wasNull;
}
}
StreamMergedResult有三个实现类
- IteratorStreamMergedResult:普通合并。
- OrderByStreamMergedResult:排序合并。
- GroupByStreamMergedResult:分组聚合合并。 其中GroupByStreamMergedResult与GroupByMemoryMergedResult类似,就不看了。
IteratorStreamMergedResult
IteratorStreamMergedResult处理普通场景的流式合并结果。比如select * from t_order,需要合并不同数据源和不同表中的数据。
public final class IteratorStreamMergedResult extends StreamMergedResult {
private final Iterator<QueryResult> queryResults;
public IteratorStreamMergedResult(final List<QueryResult> queryResults) {
this.queryResults = queryResults.iterator();
setCurrentQueryResult(this.queryResults.next());
}
@Override
public boolean next() throws SQLException {
if (getCurrentQueryResult().next()) {
return true;
}
if (!queryResults.hasNext()) {
return false;
}
setCurrentQueryResult(queryResults.next());
boolean hasNext = getCurrentQueryResult().next();
if (hasNext) {
return true;
}
while (!hasNext && queryResults.hasNext()) {
setCurrentQueryResult(queryResults.next());
hasNext = getCurrentQueryResult().next();
}
return hasNext;
}
}
需要注意的是QueryResult的迭代方式与java.util.Iterator的迭代方式不同,前者的next方法是移动游标并判断是否有数据;后者通过hasNext方法判断是否还有数据,next方法移动游标。
next方法,优先判断当前QueryResult是否还有数据,如果没有则通过Iterator移动到下一个QueryResult。
OrderByStreamMergedResult
OrderByStreamMergedResult处理排序合并的流式合并结果,思想其实就是如何将局部有序合并为全局有序。下面是OrderByStreamMergedResult的三个成员变量。
public class OrderByStreamMergedResult extends StreamMergedResult {
// SelectStatementContext中获取的orderby信息
private final Collection<OrderByItem> orderByItems;
// 元素是OrderByValue的优先级队列
private final Queue<OrderByValue> orderByValuesQueue;
// 第一次执行next方法的标志位 初始化true
private boolean isFirstNext;
}
OrderByStreamMergedResult的构造方法初始化了orderByValuesQueue优先级队列。
public OrderByStreamMergedResult(final List<QueryResult> queryResults, final SelectStatementContext selectStatementContext, final SchemaMetaData schemaMetaData) throws SQLException {
this.orderByItems = selectStatementContext.getOrderByContext().getItems();
this.orderByValuesQueue = new PriorityQueue<>(queryResults.size());
orderResultSetsToQueue(queryResults, selectStatementContext, schemaMetaData);
isFirstNext = true;
}
private void orderResultSetsToQueue(final List<QueryResult> queryResults, final SelectStatementContext selectStatementContext, final SchemaMetaData schemaMetaData) throws SQLException {
for (QueryResult each : queryResults) {
// OrderByValue持有QueryResult,可以移动QueryResult游标
OrderByValue orderByValue = new OrderByValue(each, orderByItems, selectStatementContext, schemaMetaData);
// 移动QueryResult游标并判断是否有数据
if (orderByValue.next()) {
// 放入优先队列
orderByValuesQueue.offer(orderByValue);
}
}
// 如果没有一个QueryResult有数据,会导致优先队列为空,直接返回第一个QueryResult,否则返回优先级队列中的第一个QueryResult
setCurrentQueryResult(orderByValuesQueue.isEmpty() ? queryResults.get(0) : orderByValuesQueue.peek().getQueryResult());
}
看一下OrderByValue是什么,与之前的GroupByValue类似,只不过GroupByValue没有操作游标,而OrderByValue持有QueryResult(ResultSet)。
public final class OrderByValue implements Comparable<OrderByValue> {
// 持有的QueryResult
@Getter
private final QueryResult queryResult;
// SelectStatementContext中获取的orderby信息
private final Collection<OrderByItem> orderByItems;
// 如果是字符串 每个orderby字段是否大小写敏感(用于排序比较)
private final List<Boolean> orderValuesCaseSensitive;
// 和GroupByValue#groupValues一个意思,代表当前行orderby字段列表对应的select字段值列表
private List<Comparable<?>> orderValues;
}
重点看一下OrderByValue两个方法,第一个是next方法,移动QueryResult游标,更新当前orderValues。
public boolean next() throws SQLException {
// 移动QueryResult游标并判断是否有数据
boolean result = queryResult.next();
// 如果有数据,更新获取当前行orderby字段列表对应的值列表orderValues,否则更新为空集合
orderValues = result ? getOrderValues() : Collections.emptyList();
// 返回QueryResult是否有数据
return result;
}
第二个是compareTo方法,用于优先级队列排序,循环每个orderby字段,按照排序规则、字符串大小写是否敏感,比较orderby字段值大小。
@Override
public int compareTo(final OrderByValue o) {
int i = 0;
for (OrderByItem each : orderByItems) {
int result = CompareUtil.compareTo(orderValues.get(i), o.orderValues.get(i), each.getSegment().getOrderDirection(),
each.getSegment().getNullOrderDirection(), orderValuesCaseSensitive.get(i));
if (0 != result) {
return result;
}
i++;
}
return 0;
}
回到OrderByStreamMergedResult,经过构造方法之后orderByValuesQueue优先级队列已经构造好了,队列的头部是全局排序最靠前的一行数据对应的QueryResult对应的OrderByValue。接下来看一下OrderByStreamMergedResult的next方法,逻辑都写在注释里了。
// 元素是OrderByValue的优先级队列
private final Queue<OrderByValue> orderByValuesQueue;
// 第一次执行next方法的标志位 初始化true
private boolean isFirstNext;
@Override
public boolean next() throws SQLException {
// 如果队列为空,返回false
if (orderByValuesQueue.isEmpty()) {
return false;
}
// 如果第一次执行next方法,返回true
if (isFirstNext) {
isFirstNext = false;
return true;
}
// 获取队列第一个OrderByValue
OrderByValue firstOrderByValue = orderByValuesQueue.poll();
// 判断是否还有数据 移动QueryResult指针
if (firstOrderByValue.next()) {
// 如果有则重新把OrderByValue放入优先队列
orderByValuesQueue.offer(firstOrderByValue);
}
// 队列为空返回false
if (orderByValuesQueue.isEmpty()) {
return false;
}
// 设置当前QueryResult为队列第一个OrderByValue持有的QueryResult 并返回true
setCurrentQueryResult(orderByValuesQueue.peek().getQueryResult());
return true;
}
下面举个例子select * from t_order order by user_id asc。假如按user_id%2分片,得到两个结果集,第一个结果集有两条数据user_id=1和user_id=3,第二个结果集有两条数据user_id=2和user_id=4。
构造方法会将QueryResult封装为OrderValue放入优先级队列,队列的头部是全局排序最靠前的一行数据(user_id=1)对应的QueryResult对应的OrderByValue。
第一次执行OrderByStreamMergedResult的next方法,OrderByValue1被弹出后重新放入优先级队列导致排序排在OrderByValue2之后。设置当前QueryResult为OrderByValue1对应的QueryResult1,得到当前QueryResult指向user_id=1的那一行数据。
第二次执行OrderByStreamMergedResult的next方法,OrderByValue2被弹出后重新放入优先级队列导致排序排在OrderByValue1之后。设置当前QueryResult为OrderByValue2对应的QueryResult2,得到当前QueryResult指向user_id=2的那一行数据。
第三次执行OrderByStreamMergedResult的next方法,OrderByValue1被弹出后它对应的QueryResult1不再有数据,但是OrderByValue1还会放入优先级队列(因为QueryResult的next方法是先判断有数据再移动指针)。设置当前QueryResult为OrderByValue1对应的QueryResult1,得到当前QueryResult指向user_id=3的那一行数据。
第四次执行OrderByStreamMergedResult的next方法,弹出OrderByValue1,但由于没有数据不会被放回队列。设置当前QueryResult为队列头部(peek)的OrderByValue2对应的QueryResult2,得到当前QueryResult指向user_id=4的那一行数据。
最后一次执行OrderByStreamMergedResult的next方法,弹出OrderByValue2,但由于没有数据不会被放回队列。最终判断优先级队列为空,返回false。
总结
1、流式归并?内存归并?
- 如果执行statement.executeQuery获取ResultSet,通过ConnectionMode选择。
- 如果maxConnectionsSizePerQuery(单次查询最大连接数)< sql数量,使用CONNECTION_STRICTLY连接限制。采用内存归并MemoryQueryResult,一次性读取ResultSet数据到内存,减少数据库连接开销。
- 如果maxConnectionsSizePerQuery(单次查询最大连接数)>= sql数量,使用MEMORY_STRICTLY内存限制。采用流式归并StreamQueryResult,ResultSet移动游标读取数据到内存,减少内存开销。
- 如果执行statement.getResultSet获取ResultSet,会选择使用流式归并StreamQueryResult。
2、QueryResult经过MergeEngine处理,得到归并结果MergedResult。
3、MergedResult分类
- DecoratorMergedResult:类似笛卡尔积路由引擎ShardingCartesianRoutingEngine。它只会在有合并结果之后被构造使用,用于装饰某个MergedResult。有三个实现类,都与分页有关。
- TransparentMergedResult:适配器模式,把所有的MergedResult需要实现的方法都直接委托QueryResult实现。
- MemoryMergedResult:内存归并,将数据完全读入内存集合,提供给外部元素迭代的方法。子类GroupByMemoryMergedResult处理groupby分组聚合逻辑。
- StreamMergedResult:流式归并,移动游标迭代元素。有三个实现类:
- IteratorStreamMergedResult:普通合并。
- OrderByStreamMergedResult:排序合并。
- GroupByStreamMergedResult:分组聚合合并。