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本身的查询,性能还是很差。

目前有对应的解决方案,可以参考一下: 业界难题-“跨库分页”的四种方案 。它主要采用一种方法是二次查询法。但我觉得也不是特别理想的算法。只能说,分页查询尽量不用。

参考资料:

ShardingSphere官网

一次shardingjdbc踩坑引起的胡思乱想

--------EOF---------
微信分享/微信扫码阅读