一、什么是逻辑分页
逻辑分页:先查询所有数据到内存,再从内存截取需要数据 ,属于前台分页
二、什么是物理分页
物理分页:通过SQL语句实现分页,属于后台分页
数据库分页的SQL语句写法不同:
MySQL使用limit ,SQLServer 使用top ,Oracle使用rowNum
三、优势和劣势
1、数据库负担
物理分页每次都访问数据库,逻辑分页只访问一次数据库,
物理分页对数据库造成的负担大。
2、服务器负担
逻辑分页一次性将数据读取到内存,占用了较大的内容空间,
物理分页每次只读取一部分数据,占用内存空间较小。
3、实时性
逻辑分页一次性将数据读取到内存,数据发生改变,数据库的最新状态不能实时反映到操作中,实时性差。
物理分页每次需要数据时都访问数据库,能够获取数据库的最新状态,实时性强。
4、依赖性
逻辑分页不依赖于数据库,可移植性高。
物理分页依赖于数据库SQL,可移植性差。
5、使用场合
逻辑分页主要用于数据量不大、数据稳定的场合,
物理分页主要用于数据量较大、更新频繁的场合。
四、Mybatis分页方案
1、Mybatis自带的分页方案
Mybatis自带的分页方案 通过 RowBounds 实现 先看源码:
# org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
//注意:这里传入rowBounds,跳过不需要的行 实现 offset
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
for (int i = 0; i < rowBounds.getOffset(); i++) {
//循环执行 rs.next() 跳过行 实现 offset
if (!rs.next()) {
break;
}
}
}
}
很明显 MyBatis 是属于逻辑分页的
2、PageHelper分页插件
PageHelper 通过拼接 limit 实现,属于物理分页
# com.github.pagehelper.dialect.helper.MySqlDialect
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append("\n LIMIT ? ");
} else {
sqlBuilder.append("\n LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
3、Mabatis Plus的分页插件
MP的分页插件 PaginationInterceptor 通过拼接 limit 实现,属于物理分页
public class MySqlDialect implements IDialect {
@Override
public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
String sql = originalSql + " LIMIT " + FIRST_MARK + StringPool.COMMA + SECOND_MARK;
return new DialectModel(sql, offset, limit).setConsumerChain();
}
}
五、分页方案选择
针对海量数据的场景,逻辑分页显然并不可取,如果你已经集成了MP,直接使用MP的分页插件即可,否则的话可以选择PageHelper。这两种插件有针对不同的数据库的实现,因此不用太担心可移植性差的问题。
事实上,单表数据过百万之后,offset越大速度越慢。此时可以通过子查询来优化,例如:
SELECT * FROM table order by id limit 1000000, 10;
可优化为:
SELECT * FROM table WHERE id >= (SELECT id FROM table order by id limit 1000000, 1) LIMIT 10;