shardingJdbc3.x 版本的分页bug问题解析
作者:雨翔河
引言
shardingJdbc 改名为 shardingsphere ,同时项目也已经毕业并成为 Apache 顶级项目,但是这是发现它的第二个重大 BUG,说明还是有很大的进步空间。
上次发现的重大 BUG :https://www.jb51.net/program/286235e92.htm
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 的操作
以下是截取的一段它的格式化 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,听说已经修复了,没去试过。
以上就是shardingJdbc3.x 版本的分页问题解析的详细内容,更多关于shardingJdbc版本分页的资料请关注脚本之家其它相关文章!