MyBatis Plus分页性能拉胯?这7招让你秒变优化高手!

引言

MP分页插件极大的提升了开发效率,但随着数据量从几万涨到百万、千万级,点“下一页”等半天、页面卡成PPT的情况,相信很多人遇见过。问题根源往往出在MP默认的分页逻辑——基于OFFSET+LIMIT的物理分页,在OFFSET过大时,数据库需要扫描前N条数据再丢弃,效率直线下降。今天就结合实战经验,聊聊如何用7招把分页性能从“龟速”提到“火箭”,让用户再也不用对着加载中的转圈干瞪眼!


一、先搞懂:MyBatis Plus分页为啥慢?

MP的分页原理其实很简单:通过MybatisPlusInterceptor拦截SQL,动态拼接LIMIT offset, size。但问题就出在这OFFSET上——
比如你要查第10000页(每页10条),SQL会是SELECT * FROM user LIMIT 100000, 10。这时候数据库得先把前10万条数据全扫一遍,再扔掉,最后取10条。这就像你要找书架第100层的书,得先把前99层的书全搬下来,效率能高吗?

结论OFFSET越大,性能越崩!这就是深分页问题。


二、7招优化,让分页快到飞起

第1招:深分页?改用游标分页(Cursor Pagination)

核心思路:不用OFFSET,改用上一页的最后一条数据的唯一ID作为查询条件,直接定位下一页数据。
比如用户表用自增主键id排序,第一页查完,记录最后一条的id=100,下一页直接查id > 100 LIMIT 10,数据库不用扫前面的数据!

实战代码(以MySQL自增主键为例):

// 第一页
Page<User> page1 = new Page<>(1, 10);
QueryWrapper<User> wrapper1 = new QueryWrapper<>();
wrapper1.orderByAsc("id"); // 必须按唯一递增字段排序!
mpMapper.selectPage(page1, wrapper1);
Long lastId = page1.getRecords().get(page1.getRecords().size()-1).getId(); // 记录最后一条id

// 第二页及以后
Page<User> pageN = new Page<>(n, 10);
QueryWrapper<User> wrapperN = new QueryWrapper<>();
wrapperN.gt("id", lastId).orderByAsc("id"); // 关键:用id>lastId过滤
mpMapper.selectPage(pageN, wrapperN);

注意:排序字段必须是唯一且递增的(自增主键、雪花ID都行),否则可能漏数据或重复!


第2招:少查点字段!别总用SELECT *

问题:很多人写select *,把身份证号、备注这些大字段全捞上来,网络传输、内存占用、反序列化全得跟着遭殃。
优化:用select()明确指定需要的字段,减少无效数据。

实战代码

// 只查id、name、email,其他字段不要!
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "email"); 
mpMapper.selectPage(page, wrapper);

// Lambda写法更安全(防字段名写错)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select(User::getId, User::getName, User::getEmail);

第3招:给排序和过滤字段加索引!别让数据库全表扫描

原理:如果WHERE条件或ORDER BY字段没索引,数据库得全表扫描+临时排序(filesort),慢得离谱!
优化:给常用查询字段加索引,最好建联合索引!

举个栗子
业务需求:查年龄>18岁的用户,按注册时间倒序排。
正确索引:(age, create_time)(先过滤age,再按create_time排序,一步到位)。

验证索引是否生效
EXPLAIN看SQL执行计划,重点看type(最好是refrange)和Extra(别出现Using filesort):

EXPLAIN SELECT * FROM user WHERE age > 18 ORDER BY create_time DESC LIMIT 10;

第4招:关闭总数统计!总页数真的那么重要?

痛点:MP默认会执行SELECT COUNT(*)算总记录数,100万条数据的表,这SQL可能要执行几百毫秒!
优化:如果业务不需要显示"共1000页",直接关掉总数统计!

实战代码

Page<User> page = new Page<>(1, 10);
page.setSearchCount(false); // 关键!关闭总数统计
mpMapper.selectPage(page, wrapper);
// 此时page.getTotal()是0,总页数自己算(比如当前页+是否还有下一页)

第5招:数据库特性利用:不同DB有不同优化招

不同数据库的分页语法和优化策略不一样,咱得因地制宜:

数据库传统分页SQL优化方案
MySQLLIMIT offset, size深分页改用WHERE id > lastId LIMIT size(需有序主键)
PostgreSQLLIMIT size OFFSET offset大数据量时用FETCH FIRST size ROWS ONLY(无OFFSET)
Oracle子查询+ROWNUM改用ROW_NUMBER()窗口函数,或游标分页

第6招:少JOIN!大表关联能拆就拆

问题:分页时如果JOIN了大表(比如用户表JOIN部门表),数据库得先把两个表的数据全关联一遍,再排序分页,性能直接崩!
优化

  • 小表关联:如果从表数据量小(比如部门表只有几十行),直接JOIN没问题。
  • 大表拆分:先分页主表,再用主表ID去查从表(适合从表数据按主表ID索引的情况)。
  • 冗余字段:把常用的从表字段(如部门名称)直接存到主表,避免JOIN(空间换时间)。

第7招:监控+验证:别优化完不知道有没有用!

优化后一定要验证效果,不然白忙活!推荐俩方法:

方法1:看执行计划

EXPLAIN分析SQL,确认是否用了索引,有没有filesort(全表排序)或ALL(全表扫描)。

方法2:测耗时

用数据库慢查询日志(MySQL的slow_query_log),设置阈值(比如超过1秒),监控优化前后的耗时变化。


总结:分页优化的核心就3点

  1. 减少无效扫描:深分页用游标分页,别让数据库扫前N条数据。
  2. 利用索引加速:排序、过滤字段必须有索引,最好联合索引。
  3. 降低计算量:少查字段、关总数统计、少JOIN大表。

亿级数据量别硬刚分页,要么用ES做搜索分页,要么分库分表,这才是终极解法~ 兄弟们有其他问题,评论区见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码不停蹄的玄黓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值