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

MyBatis-Plus 大表分页 count () 性能瓶颈深度解析

在使用MyBatis-Plus进行大表分页查询时你是否通过日志发现分页插件总会先执行一条count()语句且这条count()在千万级数据下耗时极长严重拖慢整体响应本文将从源码层面剖析MyBatis-Plus分页count()的执行机制结合生产实战分析性能根因并提供一套可落地的优化方案。一、背景铺垫MyBatis-Plus分页的基本流程MyBatis-Plus通过MybatisPlusInterceptor分页插件实现物理分页核心流程分为两步count查询先拦截原SQL自动生成并执行select count(0)语句获取总记录数limit分页根据总记录数和分页参数在原SQL后添加limit offset, size执行分页查询。这一机制在小表下无感知但在大表千万级下count()往往成为性能瓶颈。二、底层原理MyBatis-Plus分页count()的生成逻辑基于v3.5.5我们从源码层面看count语句是如何生成的2.1 核心拦截器MybatisPlusInterceptor分页插件的核心是MybatisPlusInterceptor它会在SQL执行前拦截调用CountExecutor生成count语句// 源码片段com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor Override public Object intercept(Invocation invocation) throws Throwable { // ... 省略前置逻辑 // 执行count查询 if (count) { countExecutor.execute(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); } // 执行分页查询 // ... 省略后续逻辑 }2.2 Count语句生成策略CountSqlParserCountSqlParser负责解析原SQL并生成count语句默认策略如下优化场景如果原SQL是单表查询且无group by、having、union等会直接优化为select count(0) from 表 where 条件默认场景否则会生成select count(0) from (原SQL) tmp子查询。问题根源默认场景下的子查询count()在大表下会导致全表扫描或临时表开销性能极差。三、生产实战问题复现与根因分析3.1 场景模拟千万级订单表我们有一张order_info表数据量1200万结构如下CREATE TABLE order_info ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_no varchar(64) NOT NULL COMMENT 订单号, user_id bigint NOT NULL COMMENT 用户ID, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint NOT NULL COMMENT 订单状态, create_time datetime NOT NULL COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;3.2 基础代码实现// 实体类 Data TableName(order_info) public class OrderInfo { TableId(type IdType.AUTO) private Long id; private String orderNo; private Long userId; private BigDecimal amount; private Integer status; private LocalDateTime createTime; } // Mapper public interface OrderInfoMapper extends BaseMapperOrderInfo { } // Service Service RequiredArgsConstructor Slf4j public class OrderInfoService { private final OrderInfoMapper orderInfoMapper; public PageOrderInfo pageOrders(int pageNum, int pageSize, Long userId) { PageOrderInfo page new Page(pageNum, pageSize); LambdaQueryWrapperOrderInfo wrapper Wrappers.lambdaQuery(); wrapper.eq(OrderInfo::getUserId, userId) .orderByDesc(OrderInfo::getCreateTime); PageOrderInfo result orderInfoMapper.selectPage(page, wrapper); log.info(分页查询完成总记录数{}, result.getTotal()); return result; } } // 分页插件配置 Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }3.3 问题复现与根因分析执行pageOrders(1, 10, 1001L)查看日志DEBUG c.b.m.e.p.p.PaginationInnerInterceptor - 执行count查询SELECT COUNT(0) FROM order_info WHERE (user_id ?) DEBUG c.b.m.e.p.p.PaginationInnerInterceptor - 执行分页查询SELECT id,order_no,user_id,amount,status,create_time FROM order_info WHERE (user_id ?) ORDER BY create_time DESC LIMIT ?看似count语句被优化了单表无group by但如果条件变为status 1无索引或者原SQL有joincount性能会骤降。更典型的慢场景原SQL有join比如关联用户表查询Select(SELECT o.*, u.username FROM order_info o LEFT JOIN user_info u ON o.user_id u.id WHERE o.user_id #{userId}) PageOrderInfoVO pageOrderVO(PageOrderInfoVO page, Param(userId) Long userId);此时MyBatis-Plus生成的count语句会是SELECT COUNT(0) FROM (SELECT o.*, u.username FROM order_info o LEFT JOIN user_info u ON o.user_id u.id WHERE o.user_id ?) tmp子查询临时表在千万级数据下count()可能耗时数秒。四、生产踩坑与避坑5大优化方案4.1 方案一自定义count语句最有效核心思路避免子查询直接写count语句利用索引。在Mapper中自定义count方法MyBatis-Plus会自动识别// Mapper接口 public interface OrderInfoMapper extends BaseMapperOrderInfo { PageOrderInfoVO selectOrderVOPage(PageOrderInfoVO page, Param(userId) Long userId); Long selectOrderVOCount(Param(userId) Long userId); }!-- OrderInfoMapper.xml -- mapper namespacecom.example.mapper.OrderInfoMapper select idselectOrderVOPage resultTypecom.example.vo.OrderInfoVO countselectOrderVOCount SELECT o.id, o.order_no, o.user_id, u.username, o.amount, o.status, o.create_time FROM order_info o LEFT JOIN user_info u ON o.user_id u.id WHERE o.user_id #{userId} ORDER BY o.create_time DESC /select select idselectOrderVOCount resultTypejava.lang.Long SELECT COUNT(0) FROM order_info o WHERE o.user_id #{userId} /select /mapper4.2 方案二利用覆盖索引优化count()核心思路让count()查询走覆盖索引避免回表。如果经常按status和create_time分页查询-- 添加覆盖索引 ALTER TABLE order_info ADD INDEX idx_status_create_time (status, create_time);此时count查询会走覆盖索引无需回表。4.3 方案三不需要精确count时使用缓存或估算核心思路对于列表页用户不关心总记录数的精确值可接受近似值或缓存。Service RequiredArgsConstructor Slf4j public class OrderInfoService { private final OrderInfoMapper orderInfoMapper; private final RedisTemplateString, Object redisTemplate; private static final String ORDER_COUNT_KEY order:count:userId:; public PageOrderInfo pageOrders(int pageNum, int pageSize, Long userId) { PageOrderInfo page new Page(pageNum, pageSize); String key ORDER_COUNT_KEY userId; Long total (Long) redisTemplate.opsForValue().get(key); if (total null) { LambdaQueryWrapperOrderInfo wrapper Wrappers.lambdaQuery(); wrapper.eq(OrderInfo::getUserId, userId); total orderInfoMapper.selectCount(wrapper); redisTemplate.opsForValue().set(key, total, 1, TimeUnit.HOURS); } page.setSearchCount(false); LambdaQueryWrapperOrderInfo wrapper Wrappers.lambdaQuery(); wrapper.eq(OrderInfo::getUserId, userId) .orderByDesc(OrderInfo::getCreateTime); PageOrderInfo result orderInfoMapper.selectPage(page, wrapper); result.setTotal(total); return result; } }4.4 方案四超大数据量使用搜索引擎Elasticsearch核心思路将数据同步到ES利用ES的count和分页能力。实现步骤将order_info表数据同步到ES使用Canal、Flink CDC或Logstash分页查询时直接调用ES的searchAPI使用from/size或search_after分页同时获取hits.total.value作为总记录数。优势ES的count性能极高即使亿级数据也能毫秒级返回。4.5 方案五分库分表后的count处理如果做了分库分表如ShardingSpherecount()需要跨库统计此时方案1使用ShardingSphere的COUNT聚合函数它会自动在各分库执行count并汇总方案2将总记录数缓存到Redis定期通过离线任务统计各分库的count并汇总更新。五、性能优化进阶从架构层面解决5.1 读写分离count查询走从库将count查询和分页查询路由到从库减轻主库压力spring: shardingsphere: datasource: names: master,slave master: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://master-host:3306/db username: root password: xxx slave: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://slave-host:3306/db username: root password: xxx rules: readwrite-splitting: style="margin-top:12px">

相关文章:

MyBatis-Plus 大表分页 count () 性能瓶颈深度解析

在使用MyBatis-Plus进行大表分页查询时,你是否通过日志发现,分页插件总会先执行一条count()语句,且这条count()在千万级数据下耗时极长,严重拖慢整体响应?本文将从源码层面剖析MyBatis-Plus分页count()的执行机制&…...

【实战】CodeBuddy使用技巧:5个Skills让编程效率翻倍的隐藏操作

目录摘要一、CodeBuddy不只是代码补全1.1 三种形态,覆盖全开发场景1.2 核心差异化二、Craft模式:一句话从0到上线2.1 实测案例:20分钟出一个完整MVP2.2 多模型切换策略2.3 Figma设计稿一键转代码三、5个效率翻倍的独有技巧3.1 技巧1&#xff…...

地理计算引擎GeographicLib全解析:从场景应用到算法原理

地理计算引擎GeographicLib全解析:从场景应用到算法原理 【免费下载链接】geographiclib Main repository for GeographicLib 项目地址: https://gitcode.com/gh_mirrors/ge/geographiclib 一、核心场景应用解析:解决真实世界地理难题 在航海导航…...

Redis 相关命令详解及其原理

Redis 相关命令详解及其原理 文章目录Redis 相关命令详解及其原理1. Redis 简介2. Redis 安装2.1 包管理器安装2.2 源码编译安装2.4 验证安装3. Redis 基础原理3.1 单线程模型3.2 底层数据结构概述4. 数据类型详解4.1 String(字符串)底层存储结构常用命令…...

Claude Code自动模式上线:AI开始自己改代码了

导读最近 Claude Code 推出了一个关键更新:自动决策模式(Auto Mode)正式上线。这次不是模型升级,而是权限变化:AI可以自行决定是否修改代码可以直接写入文件不再需要开发者逐步确认每一步操作目前已经在企业版和API用户…...

Python 数据库 ORM 实战:SQLAlchemy 详解

Python 数据库 ORM 实战:SQLAlchemy 详解 1. 背景与动机 ORM(对象关系映射)简化了数据库操作,使开发者可以使用面向对象的方式操作数据库。SQLAlchemy 是 Python 最强大的 ORM 工具,提供了灵活且功能丰富的数据库操作接…...

AI Agent架构实战教程(非常详细),从被动唤醒到主动守望,收藏这一篇就够了!

在LLM驱动的应用进入深水区后,开发者们发现:即便Agent再聪明,如果它只能停留在“你问我答”的被动模式,就永远无法触达“私人助理”的核心体验。 从OpenAI的ChatGPT Tasks到百度的“心响”产品、腾讯元宝定时任务,行业…...

3.多表关联在电商数据分析中的核心价值

多表关联在电商数据分析中的核心价值 第1章 多表关联、子查询与行列转换在电商数据分析中的核心价值 1.1 为什么单表查询不够用 我刚开始做数据分析的时候,以为SQL就是在一张表上做筛选和汇总。直到有一天,运营问我:“这批高价值用户&#xf…...

Android TTS开发避坑指南:为什么你的Google语音引擎播不出中文?从初始化到语音包管理的完整解决方案

Android TTS开发实战:解决Google语音引擎中文播报的7个关键问题 在移动应用开发中,文字转语音(TTS)功能正变得越来越重要。从无障碍辅助功能到语音导航、有声阅读,TTS技术为应用增添了更丰富的交互维度。然而,许多Android开发者在…...

OpCore-Simplify:开源系统硬件适配自动化的技术突破

OpCore-Simplify:开源系统硬件适配自动化的技术突破 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 在开源系统定制领域,硬件兼…...

MediaPipe Pose镜像体验:CPU也能毫秒级检测,无需GPU免配置

MediaPipe Pose镜像体验:CPU也能毫秒级检测,无需GPU免配置 1. 引言:CPU上的实时姿态检测革命 在计算机视觉应用中,人体姿态检测一直是个热门领域。从健身应用的动作分析到虚拟试衣的体型测量,这项技术正在改变我们与…...

w3x2lni:魔兽地图跨版本兼容解决方案技术指南

w3x2lni:魔兽地图跨版本兼容解决方案技术指南 【免费下载链接】w3x2lni 魔兽地图格式转换工具 项目地址: https://gitcode.com/gh_mirrors/w3/w3x2lni 价值定位:破解魔兽地图版本壁垒 当你尝试在1.32.8版本魔兽争霸III中运行经典的1.24.4地图时&…...

树莓派新手必看:保姆级vim安装与配置指南(含国内源切换和常见报错解决)

树莓派新手必看:保姆级vim安装与配置指南(含国内源切换和常见报错解决) 第一次接触树莓派的新手们,面对命令行操作往往既兴奋又忐忑。作为Linux系统中最强大的文本编辑器之一,vim的高效与灵活令人向往,但初…...

3步突破AI编程助手限制:免费解锁Cursor Pro高级功能全指南

3步突破AI编程助手限制:免费解锁Cursor Pro高级功能全指南 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached your…...

在QT中将多个项目(同代码不同ui和资源文件)合并

Linux下的qt环境 我现在有三个项目,代码一模一样,只有UI文件和资源文件不同现在想要合并代码 后期好上传在git 仅需要一个分支 更好管理将随行 康养 采图三个项目代码合并 思路是这样的 将每个项目都分类打包区分开我是在康养这个项目的基础上合…...

CLIP图文匹配测试工具:5分钟本地部署,零基础验证AI识图能力

CLIP图文匹配测试工具:5分钟本地部署,零基础验证AI识图能力 1. 工具简介与核心价值 你是否遇到过这样的场景:手头有一批产品图片,需要快速判断它们与哪些文字描述最匹配?或者想验证AI模型是否能准确理解图片内容&…...

QT图形界面开发集成Phi-4-mini-reasoning:打造智能桌面应用

QT图形界面开发集成Phi-4-mini-reasoning:打造智能桌面应用 1. 智能桌面应用的新可能 传统桌面应用开发正在经历一场智能化变革。想象一下,你的QT应用不仅能响应用户操作,还能理解用户意图、自动生成内容、提供智能建议——这就是集成Phi-4…...

告别照相馆!AI头像生成器教你免费制作高质量职业头像

告别照相馆!AI头像生成器教你免费制作高质量职业头像 1. 为什么选择AI生成职业头像? 在当今数字化求职环境中,一张专业的头像照片已经成为简历不可或缺的部分。传统照相馆拍摄存在三个主要痛点: 成本高昂:专业摄影工…...

【分布式理论】CAP PACELC

文章目录一、引言二、CAP理论2.1 CAP分别指的是什么?2.2 CAP 的经典表述:三选二2.1 CP 系统(牺牲可用性,保证一致性)2.2 AP 系统(牺牲一致性,保证可用性)三、PACELC理论3.1 PACELC的…...

【C++:哈希表】从哈希冲突到负载因子:深入探索开放定址与链地址法的核心机密

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》《C入门到进阶&自我学习过程记录》 《算法题讲解指南》--优选算法 《算法题讲解指南》--递归、搜索与回溯算法 《算法题讲解指南》--动态规划算法 ✨未择之路&#xff0…...

终极指南:3步用VR-Reversal将3D视频转为2D,普通设备也能自由探索VR世界

终极指南:3步用VR-Reversal将3D视频转为2D,普通设备也能自由探索VR世界 【免费下载链接】VR-reversal VR-Reversal - Player for conversion of 3D video to 2D with optional saving of head tracking data and rendering out of 2D copies. 项目地址…...

Comsol 脉冲激光诱导等离子体仿真模型:探索微观世界的奇妙之旅

Comsol脉冲激光诱导等离子体仿真模型 利用脉冲激光作为热源,在氩气环境中诱导产生等离子体,主要体现出等离子体的密度、等离子体温度等参数 可以为激光诱导等离子体提供准确的参考在科研与工程领域,对脉冲激光诱导等离子体的深入研究有着举足…...

YOLOv8显存溢出?CPU轻量版部署教程让资源占用降低80%

YOLOv8显存溢出?CPU轻量版部署教程让资源占用降低80% 1. 项目背景与价值 你是不是遇到过这样的情况:想用YOLOv8做目标检测,结果一运行就显存溢出,或者GPU资源被占满导致其他程序卡顿?这种情况在资源有限的开发环境中…...

基于IEEE39节点系统的风力发电机组并网改造与稳定性研究

基于IEEE39节点系统的风力发电机组并网改造与稳定性研究 摘要 随着可再生能源在电力系统中占比的不断提升,风电并网技术已成为电力系统领域的研究热点。本文针对IEEE39节点标准测试系统,将其工作频率从60Hz改造为50Hz,并将30、32、34、37号节点的同步发电机分别替换为不同…...

5个关键步骤:OpenCore Legacy Patcher让老旧Mac焕发新生

5个关键步骤:OpenCore Legacy Patcher让老旧Mac焕发新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher是一款基于Pytho…...

Qwen-Ranker Pro快速部署:Windows WSL2环境下Streamlit兼容性方案

Qwen-Ranker Pro快速部署:Windows WSL2环境下Streamlit兼容性方案 1. 环境准备与系统要求 在Windows WSL2环境中部署Qwen-Ranker Pro需要确保系统满足以下基本要求: 硬件要求: 内存:至少8GB RAM(推荐16GB以上&…...

决策树剪枝实战:用C++和Python分别实现,我踩过的坑你别再踩了

决策树剪枝实战:用C和Python分别实现,我踩过的坑你别再踩了 第一次在C里实现决策树剪枝时,内存泄漏让我调试到凌晨三点;而用Python重写时,又因为没注意NumPy的广播机制导致准确率计算全错。这篇文章记录了我从零实现两…...

PakePlus云打包入门指南:从零到一的GitHub Token配置与安全实践

PakePlus云打包入门指南:从零到一的GitHub Token配置与安全实践 【免费下载链接】PakePlus Turn any webpage/HTML/Vue/React and so on into desktop and mobile app under 5M with easy in few minutes. 轻松将任意网站/HTML/Vue/React等项目构建为轻量级(小于5M)…...

3步打造B站高效体验:开源客户端的极致优化指南

3步打造B站高效体验:开源客户端的极致优化指南 【免费下载链接】BiliBili-UWP BiliBili的UWP客户端,当然,是第三方的了 项目地址: https://gitcode.com/gh_mirrors/bi/BiliBili-UWP BiliBili-UWP作为一款开源客户端,专为Wi…...

LangChain + AgentRun 浏览器沙箱极简集成指南

AgentRun Browser Sandbox 介绍 什么是 Browser Sandbox? Browser Sandbox 是 AgentRun 平台提供的云原生无头浏览器沙箱服务,基于阿里云函数计算(FC)构建。它为智能体提供了一个安全隔离的浏览器执行环境,支持通过标准的 Chrome DevTools Protocol (…...