分页并不是将所有数据查询出来后再分页,而是通过数据库的 LIMIT 语句直接从数据源中提取当前页的数据。MyBatis 分页插件(如 PageHelper) 的核心工作就是拦截原始 SQL 查询,并在 SQL 中加入分页逻辑(如 LIMIT 和 OFFSET),避免一次性加载所有数据到内存中。
分页的本质
分页的核心是在 SQL 查询层面加以限制,而不是在内存中对查询结果分页。例如:
假设有 10,000 条员工记录,而页面每次只显示 10 条:
- 错误的分页方式:
- 查询所有 10,000 条记录到内存。
- 在内存中取出前 10 条,忽略剩余的 9,990 条。
- 问题: 数据量大时,内存消耗严重,查询效率低下。
- 正确的分页方式(数据库分页):
- 通过 SQL 的
LIMIT和OFFSET限制查询结果。 - 只从数据库中读取 10 条记录,传递给前端。
- 效果: 数据库层面减少了无用数据的传输和加载,大幅提高性能。
- 通过 SQL 的
MyBatis 分页插件的实现
分页插件(如 PageHelper)会自动拦截原始 SQL,并在其基础上动态追加 LIMIT 和 OFFSET 子句。例如:
- 原始 SQL:
SELECT * FROM user; - 分页插件自动追加:
SELECT * FROM user LIMIT 10 OFFSET 20; -- 查询第 3 页(每页 10 条)
代码示例:
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll(); // 原始查询
PageInfo<User> pageInfo = new PageInfo<>(users);
这里的 startPage 方法会在 selectAll 执行前拦截 SQL 并附加分页参数。
避免一次性查询过多数据的措施
- 数据库层分页
- 分页插件会直接在数据库层面执行分页 SQL,只加载当前页的数据到内存中,而不是加载全部数据。
- 优点: 数据传输量和内存消耗仅与分页大小相关,与总数据量无关。
- 限制分页大小
- 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
- 每页数据量
pageSize不超过 100 条,避免单次请求过多数据。
- 每页数据量
- 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
分页并不是将所有数据查询出来后再分页,而是通过数据库的 LIMIT 语句直接从数据源中提取当前页的数据。MyBatis 分页插件(如 PageHelper) 的核心工作就是拦截原始 SQL 查询,并在 SQL 中加入分页逻辑(如 LIMIT 和 OFFSET),避免一次性加载所有数据到内存中。
分页的本质
分页的核心是在 SQL 查询层面加以限制,而不是在内存中对查询结果分页。例如:
假设有 10,000 条员工记录,而页面每次只显示 10 条:
- 错误的分页方式:
- 查询所有 10,000 条记录到内存。
- 在内存中取出前 10 条,忽略剩余的 9,990 条。
- 问题: 数据量大时,内存消耗严重,查询效率低下。
- 正确的分页方式(数据库分页):
- 通过 SQL 的
LIMIT和OFFSET限制查询结果。 - 只从数据库中读取 10 条记录,传递给前端。
- 效果: 数据库层面减少了无用数据的传输和加载,大幅提高性能。
- 通过 SQL 的
SQL 示例:
sql复制代码SELECT * FROM user
LIMIT 10 OFFSET 0; -- 第 1 页
SELECT * FROM user
LIMIT 10 OFFSET 10; -- 第 2 页
MyBatis 分页插件的实现
分页插件(如 PageHelper)会自动拦截原始 SQL,并在其基础上动态追加 LIMIT 和 OFFSET 子句。例如:
- 原始 SQL:sql复制代码
SELECT * FROM user; - 分页插件自动追加:sql复制代码
SELECT * FROM user LIMIT 10 OFFSET 20; -- 查询第 3 页(每页 10 条)
代码示例:
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll(); // 原始查询
PageInfo<User> pageInfo = new PageInfo<>(users);
这里的 startPage 方法会在 selectAll 执行前拦截 SQL 并附加分页参数。
避免一次性查询过多数据的措施
- 数据库层分页
- 分页插件会直接在数据库层面执行分页 SQL,只加载当前页的数据到内存中,而不是加载全部数据。
- 优点: 数据传输量和内存消耗仅与分页大小相关,与总数据量无关。
- 限制分页大小
- 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
- 每页数据量
pageSize不超过 100 条,避免单次请求过多数据。
- 每页数据量
- 示例:java复制代码
if (pageSize > 100) { pageSize = 100; // 限制每页最大数据量 }
- 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
- 索引优化
- 数据量大时,为分页查询的条件字段(如主键
id或其他排序字段)建立索引。 - 示例:sql复制代码
CREATE INDEX idx_user_id ON user(id);
- 数据量大时,为分页查询的条件字段(如主键
- 增量查询(滚动分页)
- 当数据量极大时(如百万级),可使用滚动分页(基于游标),避免 OFFSET 导致的性能问题。
- 示例:sql复制代码
SELECT * FROM user WHERE id > 1000 LIMIT 10; -- 滚动分页
总结:MyBatis 分页避免一次性查询的关键点
- 分页 SQL 自动生成:
- 分页插件通过拦截 SQL 自动加上
LIMIT和OFFSET参数,直接在数据库层面分页。
- 分页插件通过拦截 SQL 自动加上
- 只加载当前页的数据:
- 数据库返回的数据量与分页大小相关,不会加载全部数据到内存。
- 避免过大的分页大小:
- 限制每页的数据量,例如设置
pageSize <= 100。
- 限制每页的数据量,例如设置
- 高效索引支持:
- 确保查询条件字段(如
id或department)有索引,提升分页查询效率。
- 确保查询条件字段(如
通过这些措施,分页查询不会一次性加载所有数据,而是高效地从数据库中提取当前页的数据,从而避免性能问题。
注意
前端在分页过程中,每次换页通常都需要向后端发送一个新的请求,以获取对应页的数据。这种方式称为 服务端分页(Server-side Pagination),它是处理大数据量时的最佳实践。
前端分页 vs 服务端分页
1. 前端分页(Client-side Pagination)
- 特点:
前端一次性加载所有数据到内存中,分页逻辑由前端实现(通过 JavaScript 操作数组进行切片)。 - 优点:
- 换页不需要向后端发请求,页面切换速度快。
- 缺点:
- 数据量大时(如 10,000 条),初始加载速度慢,前端内存占用大。
- 数据的实时性差,无法动态获取后端更新的数据。
2. 服务端分页(Server-side Pagination)
- 特点:
前端每次换页都向后端发送请求,后端通过分页查询(LIMIT和OFFSET)返回当前页的数据。 - 优点:
- 数据量大时性能优异,初始加载快,前端内存占用小。
- 支持数据的动态更新,每次获取最新数据。
- 缺点:
- 每次换页需要发送网络请求,受网络延迟影响。
- 需要后端实现分页逻辑。
服务端分页前端也要分页展示
前端分页代码uni-app:
data() {
return {
users: [], // 用户列表
pageNum: 1, // 当前页码
pageSize: 5, // 每页显示条数
pages: 1, // 总页数
};
},
methods: {
// 获取分页数据
getPage(page) {
if (page < 1 || page > this.pages) return;
uni.request({
url: "http://localhost:8081/user/page",
method: "GET",
data: {
pageNum: page,
pageSize: this.pageSize,
},
success: (res) => {
if (res.statusCode === 200) {
const data = res.data;
this.users = data.list; // 更新用户列表
this.pageNum = data.pageNum; // 更新当前页码
this.pages = data.pages; // 更新总页数
} else {
uni.showToast({
title: "数据加载失败",
icon: "none",
});
}
},
fail: () => {
uni.showToast({
title: "请求失败,请检查网络",
icon: "none",
});
},
});
},
后端分页:
Controller层:
@GetMapping(value = "/page")
public ResponseEntity<PageInfo<User>> getUsersByPage(
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "5") int pageSize) {
PageInfo<User> pageInfo = userSerive.selectUsersByPage(pageNum, pageSize);
if (pageInfo.getList().isEmpty()) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
}
return ResponseEntity.ok(pageInfo);
}
Mapper层:
// 分页查询
List<User> selectByPage(@Param("offset") int offset, @Param("limit") int limit);
暂时没有回复