sharding-jdbc原理学习
sharding-jdbc作为ShardingSphere的一部分,是Apache的孵化项目。虽然当当网现在正在进行夫妻间的狗血乱斗,但ShardingShere的确还是很牛逼的。看过这个负责人写的文章水平也很高。所以,任何事情都不能一棒子打死,阿里也有垃圾,当当也有大神。
源码阅读一定是带着问题去读,否则只会像无头苍蝇那样到处乱幢。
首先就是分页查询。Sharding-JDBC必须要解决的两个问题是:
1、分页查询的性能优化问题。由于本身limit查询就是一个性能极差的查询,limit越大,性能越差。
2、由于分库分表,要确保归并结果获取到的数据是准确的。
在单库单表中,如果我们想要提高分页查询效率,一般都会再增加一个查询条件,比如使用index之前最大的主键id,就可以不用搜索前面所有的数据。
但是在分库分表中,这并不一定可以。虽然也可以有全局唯一主键,比如sharding-jdbc可以根据雪花或者UUID生成分布式主键,但是像单库单表的分页方法可能会导致归并的结果不准确。这个在ShardingSphere的官网有介绍,就不赘述了,下面的参考资料有。
其做法是:假如你的原始sql是 limit 3,10,它会将其转化为 limit 0,13。是的,它会从头开始查出13条数据,确保归并之后的数据是正确的。
看一下相应的源码。网上的很多都是老版本的了。我看的是4.0.0,它把分页信息都放到pangationContext这里面了。
public final class ShardingPaginationParameterRewriter implements ParameterRewriter, SQLRouteResultAware {
private SQLRouteResult sqlRouteResult;
@Override
//判断是否需要分页。条件:具有分页条件,且不是单片路由,可能涉及到跨库
public boolean isNeedRewrite(final SQLStatementContext sqlStatementContext) {
return sqlRouteResult.getSqlStatementContext() instanceof SelectSQLStatementContext
&& ((SelectSQLStatementContext) sqlRouteResult.getSqlStatementContext()).getPaginationContext().isHasPagination() && !sqlRouteResult.getRoutingResult().isSingleRouting();
}
@Override
public void rewrite(final ParameterBuilder parameterBuilder, final SQLStatementContext sqlStatementContext, final List<Object> parameters) {
PaginationContext pagination = ((SelectSQLStatementContext) sqlRouteResult.getSqlStatementContext()).getPaginationContext();
Optional<Integer> offsetParameterIndex = pagination.getOffsetParameterIndex();
//这个地方就是要重写offset
if (offsetParameterIndex.isPresent()) {
rewriteOffset(pagination, offsetParameterIndex.get(), (StandardParameterBuilder) parameterBuilder);
}
Optional<Integer> rowCountParameterIndex = pagination.getRowCountParameterIndex();
if (rowCountParameterIndex.isPresent()) {
//重写所需行数
rewriteRowCount(pagination, rowCountParameterIndex.get(), (StandardParameterBuilder) parameterBuilder);
}
}
private void rewriteOffset(final PaginationContext pagination, final int offsetParameterIndex, final StandardParameterBuilder parameterBuilder) {
parameterBuilder.addReplacedParameters(offsetParameterIndex, pagination.getRevisedOffset());
}
private void rewriteRowCount(final PaginationContext pagination, final int rowCountParameterIndex, final StandardParameterBuilder parameterBuilder) {
parameterBuilder.addReplacedParameters(rowCountParameterIndex, pagination.getRevisedRowCount((SelectSQLStatementContext) sqlRouteResult.getSqlStatementContext()));
}
}
看看具体的重新offset和limit:
/**
* Get revised offset.
*
* @return revised offset
*/
public long getRevisedOffset() {
//就直接置为0
return 0L;
}
/**
* Get revised row count.
*
* @param shardingStatement sharding optimized statement
* @return revised row count
*/
public long getRevisedRowCount(final SelectSQLStatementContext shardingStatement) {
if (isMaxRowCount(shardingStatement)) {
return Integer.MAX_VALUE;
}
//offset + 原始行数
return rowCountSegment instanceof LimitValueSegment ? actualOffset + actualRowCount : actualRowCount;
}
其实,我们可以看到,这种解决方式,查询数据本身是没有问题,但性能确大大下降,offset越大,数据越多,性能越差。尽管它进行了流式处理以及归并排序,但这也只是提高归并环节的效率,对Mysql本身的查询,性能还是很差。
目前有对应的解决方案,可以参考一下: 业界难题-“跨库分页”的四种方案 。它主要采用一种方法是二次查询法。但我觉得也不是特别理想的算法。只能说,分页查询尽量不用。
参考资料:
微信分享/微信扫码阅读