雨翔河
首页
列表
关于
shardingJdbc3.x 版本的分页问题
2020-06-18 11:38
shardingJdbc 改名为 shardingsphere ,同时项目也已经毕业并成为 Apache 顶级项目,但是这是发现它的第二个重大BUG,说明还是有很大的进步空间。 上次发现的重大BUG可以看我的博客: [shardingsphered 的线程安全问题](https://yuxianghe.net/blog/64) BUG的表象是在使用 shardingJdbc3.1.0 进行分页查询的时候存在问题,一般情况下sql是这样的格式 `limit x,y ` ,但是不管怎么翻页查询,x的值一直都是0,这会导致查询出来的结果,每一次翻页的数据会越来越多,都包含了前一页的数据。 首先,使用 shardingJdbc 的情况下,理论上就不应该存在分页查询。 原因可以想象使用 shardingJdbc 是为了分表,既然是分表了那么这种查询必然会通过条件查询所有涉及到的表来得出结果,然后聚合到内存来进行分页,这会使得机器的压力是巨大的。 所以如果不涉及到分表的情况下,不应该是用 shardingJdbc,如果使用了分表就不应该这样搞分页查询。 前提条件是理想的情况下,但是在某些情况下,误用了导致没有分表策略的表使用了 shardingJdbc 来进行操作也是有可能的。 shardingJdbc3.1.0 在处理sql中含有 `limit x,y ` 这种操作的时候会进行特殊处理。 类名: `io.shardingsphere.core.routing.router.sharding.ParsingSQLRouter` 以下是截取的一段它的格式化SQL和处理limit的操作。 ``` @Override public SQLRouteResult route(final String logicSQL, final List<Object> parameters, final SQLStatement sqlStatement) { ...... if (sqlStatement instanceof SelectStatement && null != ((SelectStatement) sqlStatement).getLimit()) { processLimit(parameters, (SelectStatement) sqlStatement); } ...... } private void processLimit(final List<Object> parameters, final SelectStatement selectStatement) { boolean isNeedFetchAll = (!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems(); selectStatement.getLimit().processParameters(parameters, isNeedFetchAll, databaseType); } ``` 其中 selectStatement.getLimit() 得到的Limit类的实现有点问题,这个导致了问题就很大了。 类名: `io.shardingsphere.core.parsing.parser.context.limit.Limit` 以下是截取的一段它的实现方法 ``` * @param parameters parameters * @param isFetchAll is fetch all data or not * @param databaseType database type */ public void processParameters(final List<Object> parameters, final boolean isFetchAll, final DatabaseType databaseType) { fill(parameters); rewrite(parameters, isFetchAll, databaseType); } private void fill(final List<Object> parameters) { int offset = 0; if (null != this.offset) { offset = -1 == this.offset.getIndex() ? getOffsetValue() : NumberUtil.roundHalfUp(parameters.get(this.offset.getIndex())); this.offset.setValue(offset); } int rowCount = 0; if (null != this.rowCount) { rowCount = -1 == this.rowCount.getIndex() ? getRowCountValue() : NumberUtil.roundHalfUp(parameters.get(this.rowCount.getIndex())); this.rowCount.setValue(rowCount); } if (offset < 0 || rowCount < 0) { throw new SQLParsingException("LIMIT offset and row count can not be a negative value."); } } private void rewrite(final List<Object> parameters, final boolean isFetchAll, final DatabaseType databaseType) { int rewriteOffset = 0; int rewriteRowCount; if (isFetchAll) { rewriteRowCount = Integer.MAX_VALUE; } else if (isNeedRewriteRowCount(databaseType)) { rewriteRowCount = null == rowCount ? -1 : getOffsetValue() + rowCount.getValue(); } else { rewriteRowCount = rowCount.getValue(); } if (null != offset && offset.getIndex() > -1) { parameters.set(offset.getIndex(), rewriteOffset); } if (null != rowCount && rowCount.getIndex() > -1) { parameters.set(rowCount.getIndex(), rewriteRowCount); } } ``` 在对limit语法进行处理的时候,会重写掉这个offset,使得offset=0,这就导致了每次向后翻页操作的数据会把前一次的分页查询结果也带上,每向后翻一页就会还是从0开始读取数据。 所以,解决方案就是如果不是分表就不要去用shardingJdbc,如果是分表就不要去分页查询,性能损耗太严重,毕竟它得聚合到内存之后再进行分页处理,这个数据量过大的时候真的很容易出事。 如果一定要这么玩,那么看下shardingJdbc的新版本4.x是否修复了这个BUG,听说已经修复了,没去试过。
类型:工作
标签:shardingJdbc,shardingsphere,java
Copyright © 雨翔河
我与我周旋久
独孤影
开源实验室