当前位置: 首页 > news >正文

大数据量下分页查询性能优化实践(SpringBoot+MyBatis-Plus)

  • 在日常开发中,分页查询是高频需求。但当数据量达到百万、千万级时,普通分页方式往往会出现性能瓶颈。
  • 本文基于SpringBoot+MyBatis-Plus技术栈,从初级到高级逐步讲解大数据量下分页查询的优化方案,帮助大家解决分页性能问题。

一、分页查询的核心挑战

在大数据量场景下,传统分页方式主要面临两个问题:

  1. count查询耗时:为了获取总页数,需要执行select count(*),当表数据量极大时,全表扫描的count操作会非常缓慢。
  2. limit offset性能衰减limit offset, size语法中,offset越大,数据库需要扫描越多的数据(先跳过offset条再取size条),当offset达到十万、百万级时,性能会急剧下降。

二、初级方案:MyBatis-Plus原生分页

MyBatis-Plus提供了便捷的分页插件,适合数据量较小(万级以内)的场景,用法简单直接。

1. 配置分页插件

首先需要在SpringBoot中配置MyBatis-Plus的分页插件:

@Configuration
public class MyBatisConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件,指定数据库类型(MySQL为例)interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

2. 基础分页查询

在Service中直接使用Page对象进行分页:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic IPage<User> getUserPage(Integer pageNum, Integer pageSize) {// 创建分页对象(pageNum:页码,pageSize:每页条数)Page<User> page = new Page<>(pageNum, pageSize);// 执行分页查询(条件构造器可根据需求添加)return baseMapper.selectPage(page, null);}
}

3. 优缺点分析

  • 优点:开箱即用,无需手动写limit语句,MyBatis-Plus自动处理分页逻辑。
  • 缺点
    • 自动执行count(*)查询,大数据量下耗时明显。
    • 底层仍依赖limit offset, size,offset增大时性能下降。

三、中级方案:优化count与limit

当数据量达到十万级以上时,初级方案的性能问题开始显现,此时可通过优化count查询和limit用法提升性能。

1. 禁用count查询

如果业务不需要总页数(例如“加载更多”场景),可禁用count查询:

@Override
public IPage<User> getUserPageWithoutCount(Integer pageNum, Integer pageSize) {Page<User> page = new Page<>(pageNum, pageSize);// 禁用count查询page.setOptimizeCountSql(false);page.setSearchCount(false); // 不执行countreturn baseMapper.selectPage(page, null);
}

适用场景:移动端“加载更多”、无需显示总页数的列表页。

2. 直接使用limit语句

MyBatis-Plus的last()方法可直接拼接SQL,避免框架自动生成的复杂分页逻辑:

@Override
public List<User> getUserByLimit(Integer pageNum, Integer pageSize) {Integer offset = (pageNum - 1) * pageSize;// 使用lambdaQuery拼接limitreturn lambdaQuery().last("limit " + offset + "," + pageSize) // 直接拼接limit.list();
}

注意:手动拼接SQL需注意SQL注入风险,offset和pageSize需确保为整数类型。

3. 优化count查询

如果必须要总页数,可优化count查询:

  • 给count查询的条件字段加索引(避免全表扫描)。
  • count(1)替代count(*)(在InnoDB中性能差异不大,但部分场景有优化)。
  • 对于分表场景,可通过汇总各分表count结果减少单表扫描压力。

四、高级方案:游标分页(基于唯一键)

当数据量达到百万级以上,limit offset的性能问题会非常突出(offset=100万时,数据库需要扫描100万+条数据)。此时推荐使用游标分页(Cursor Pagination)。

1. 原理

游标分页基于唯一有序字段(如自增ID、创建时间+唯一键),以上一页的最后一条数据的字段值作为“游标”,定位下一页的起点,避免使用offset。

例如:上一页最后一条数据的ID是1000,下一页就查询id > 1000的前10条数据。

2. 实现(自增ID场景)

@Override
public List<User> getNextPage(Long lastId, Integer pageSize) {// 以lastId为游标,查询下一页return lambdaQuery().gt(lastId != null, User::getId, lastId) // 大于上一页最后一个ID.orderByAsc(User::getId) // 确保排序一致.last("limit " + pageSize).list();
}

调用方式

  • 第一页:lastId = null,查询前N条数据。
  • 后续页:将上一页最后一条数据的ID作为lastId传入。

3. 优缺点分析

  • 优点
    • 性能稳定:无论分页到第几页,都是基于索引的范围查询(id > ?),效率极高。
    • 无重复/漏数据风险:基于唯一键定位,避免因分页过程中数据新增/删除导致的重复或漏数据。
  • 缺点
    • 不支持“跳页查询”(如直接跳转到第100页),仅支持“上一页/下一页”。
    • 依赖有序唯一字段。

五、特殊场景:主键为UUID的处理+游标分页

如果主键是UUID(无序),无法直接作为游标字段,此时需通过以下方式解决:

1. 新增有序唯一字段

在表中新增一个自增序列字段(如sequence_id)或带索引的创建时间字段(create_time),确保其有序且唯一。

// 基于create_time + id(确保唯一)实现游标分页
@Override
public List<User> getNextPageByCreateTime(LocalDateTime lastCreateTime, String lastId, Integer pageSize) {LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();if (lastCreateTime != null && lastId != null) {// 组合条件:create_time > lastCreateTime 或者 (create_time = lastCreateTime 且 id > lastId)queryWrapper.and(w -> w.gt(User::getCreateTime, lastCreateTime).or(w2 -> w2.eq(User::getCreateTime, lastCreateTime).gt(User::getId, lastId)));}return queryWrapper.orderByAsc(User::getCreateTime).orderByAsc(User::getId) // 确保同一时间下的排序唯一.last("limit " + pageSize).list();
}

2. 注意事项

  • create_time需加索引,否则范围查询仍会慢。
  • 必须通过“时间+ID”组合确保唯一性,避免同一时间有多个数据时的分页错乱。

六、极致优化:结合业务场景的方案

  1. 分库分表场景

    • 用Sharding-JDBC等中间件,分页时先从各分表获取分页数据,再聚合结果。
    • 避免跨库count(可通过估算或缓存总条数)。
  2. 缓存热门分页

    • 对首页、前几页等高频访问的分页结果进行缓存(如Redis)。
    • 缓存时间根据数据更新频率调整。
  3. 异步预加载

    • 当用户浏览第N页时,异步预加载第N+1页数据,提升用户体验。

七、方案选择建议

数据量推荐方案适用场景
万级以内MyBatis-Plus原生分页后台管理系统、需总页数展示
十万级禁用count+优化limit无需总页数的列表页
百万级以上游标分页移动端加载更多、大数据列表

总结

大数据量下的分页优化核心是减少扫描数据量避免全表操作。实际开发中,需根据数据量、业务场景(是否需要跳页、总页数)选择合适的方案,而非盲目追求“高级方案”。结合索引优化、缓存策略等手段,才能真正实现高性能的分页查询。

http://www.dtcms.com/a/325279.html

相关文章:

  • 基于Spring Data Elasticsearch的分布式全文检索与集群性能优化实践指南
  • Rust:anyhow 高效错误处理库核心用法详解
  • Rust 实战五 | 配置 Tauri 应用图标及解决 exe 被识别为威胁的问题
  • 新人该如何将不同的HTML、CSS、Javascript等文件转化为Vue3文件架构
  • 零基础学习jQuery第三天
  • 探秘华为:松山湖的科技与浪漫之旅
  • SQL 基础查询语句详解
  • DDIA第五章:分布式数据复制中的一致性与冲突处理
  • 基于STM32设计的矿山环境监测系统(NBIOT)_262
  • (25.08)Ubuntu20.04复现KISS-ICP
  • 【TensorRT踩坑记录】安装与使用
  • 软考 系统架构设计师系列知识点之杂项集萃(121)
  • 数据变而界面僵:Vue/React/Angular渲染失效解析与修复指南
  • leetcode-hot-100 (图论)
  • 算法训练营DAY57 第十一章:图论part07
  • 基于Qt Property Browser的通用属性系统:Any类与向量/颜色属性的完美结合
  • CVE-2019-0708复刻
  • react+vite-plugin-react-router-generator自动化生成路由
  • OBOO鸥柏丨115寸商用屏/工业液晶显示器招标投标核心标底参数要求
  • 【JAVA】使用系统音频设置播放音频
  • MyBatis执行器与ORM特性深度解析
  • React18 Transition特性详解
  • ARM汇编
  • Apache IoTDB 全场景部署:跨「端-边-云」的时序数据库 DB+AI 实战
  • 一维码+二维码+字符识别
  • 【数据结构】深入理解顺序表与通讯录项目的实现
  • 第十六届蓝桥杯大赛青少组 C++ 省赛真题解析(2025年8月10日)
  • 动态创建可变对象:Python类工厂函数深度解析
  • 云原生环境 Prometheus 企业级监控实战
  • 本地文件夹与 GitHub 远程仓库绑定并进行日常操作的完整命令流程