分页数据不准问题分析与解决

大纲 📖

  • 1、场景 🪵
  • 2、原因 🔥
  • 3、解决方式:游标分页 📏
  • 4、一点思考💡
  • 5、全表查询的优化思路 🍅

记录一个分页不准的问题

1、场景 🪵

调用一个第三方List接口(带分页),然后遍历分页后的每一批数据做处理,结果发现代码会偶现最终处理数据不全的bug

2、原因 🔥

对于需要获取全量数据的场景,常见的有两种实现途径:

  • 全量查表,不推荐,因为数据量很大时,数据库压力和服务内存压力都很大
select * from table;
  • 分页查表,一批批的拿数据,最终拿到全量数据

对于一个普通的分页接口,底层常规的都是类似:

select * from table limit 10 offset 0

这样,在我们一批批拿数据的过程中,如果有数据插入或者删除,就会导致漏数据或者一些数据被重复获取,比如下面这个分页,

-- 第一页
SELECT * FROM users ORDER BY id LIMIT 0,10;
-- 第二页
SELECT * FROM users ORDER BY id LIMIT 10,10;
  • 数据删除:当第一页查询后,有人删除了第8条数据。此时再查第二页,原第11条变成第10条,导致第11条数据被跳过(实际返回原第12~21条)

  • 数据插入:当第一页查询后,有人插入5条新数据。此时再查第二页,会重复显示原第6~15条(因为新插入数据导致原数据位置后移)

因此,如果List遍历所有分页获取全量数据的过程中,有数据增删,就会导致分页数据不准,特别是数据增删频繁的情况下,这个情况基本就是必现

3、解决方式:游标分页 📏

以一个主键自增的表为例:

idname
1tom
2cat
3dog

我们可以考虑给原本的select语句加一个where条件过滤,再取limit行的数据,offset这个起始位置值,容易受数据增删的影响,但这个where条件,就像一个游标卡尺的左臂,明确记录了每次取值的位置:

-- 第一页
SELECT * FROM users 
WHERE id > 0  -- 初始游标
ORDER BY id LIMIT 5;-- 返回最后一条ID=5,作为下一页游标-- 第二页(不受中间变更影响)
SELECT * FROM users 
WHERE id > 5  -- 使用上一页最后ID
ORDER BY id LIMIT 5;

此时,如果id = 2、id = 3、id = 4这条数据被删了,常规的offset和limit下,id = 6、id = 7、id = 8这三条数据就会被漏掉,但有游标id > 5,可以精准定位到后面的数据,然后limit 5取5条,就不会漏数据,且主键ID自带索引,性能也好

4、一点思考💡

你可能会想到数据库的事务隔离级别,但这个其实没用:

@Transactional(isolation = Isolation.REPEATABLE_READ)
public List<User> getUsers(int page) {// 同一事务内所有查询看到相同数据快照return userMappere.findByPage(PageRequest.of(page, 5));
}

可重复读,是一次事务没结束的时候,或者说同一个事务里,每次读到的结果都一样,但我执行一次limit 和offset查询,方法执行一次,就是一个完整事务,所以,我多次传不同limit和offset获取一批批的数据时,就不是一个事务,还是会漏数据,因此,这地方修改事务隔离级别也不行

5、全表查询的优化思路 🍅

全表查询改分页后,如果拉一页数据,处理完再拉一页数据,过程长,容易出现增删,导致分页不准。当表结构不支持游标分页时,可以考虑下:循环分批,查询全量数据到内存后,再慢慢处理,这种方式,虽然数据库压力不大,但还是得考虑你服务自己内存的压力,加载太多对象存Jvm内存,其实并不是最优解,实现:

// 一次取limit900,让扫描行数 < 1000
private static final int BATCH_SIZE = 900;
// 最大安全页数限制,防止意外无限循环
private static final int MAX_SAFE_PAGES = 1000;/*** 统一拉取,避免源数据增删频繁,分页不准* 当前最大数据量 < 2w*/
private static <T> List<T> fetchAllByBatch(int batchSize, BiFunction<Integer, Integer, List<T>> batchQueryFunction) {List<T> result = new ArrayList<>();int offset = 0;int pageCount = 0;while (pageCount < MAX_SAFE_PAGES) {pageCount++;List<T> batch = batchQueryFunction.apply(offset, batchSize);if (batch == null || batch.isEmpty()) {break;}result.addAll(batch);offset += batchSize;// 不够一批了,那后面肯定也没数据了,跳出循环即可if (batch.size() < batchSize) {break;}}return result;
}

上面通过一个BiFunction函数式接口,传入offset和limit,调用apply方法,拉取到分页的数据,然后存下来,接着立马去拉下一批,直到全表数据拉完

@FunctionalInterface
public interface BiFunction<T, U, R> {/*** Applies this function to the given arguments.** @param t the first function argument* @param u the second function argument* @return the function result*/R apply(T t, U u);
}

此时,之前的全表select,就可以改成:

@Repository
public interface UserMapper {@Select("select `uid`, `uname`, `age` from user_table limit #{limit} offset #{offset}")List<User> getByBatch(int offset, int limit);}
// Service层
List<User> allUserData = fetchAllByBatch(BATCH_SIZE, UserMapper::getByBatch);

这并不是最优解,因为一来要考虑Jvm内存压力,二来并不是100%不漏数据,只是缓解,这里记录下,主要是对BiFunction接口的使用,有一定的抽象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/84222.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/84222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis原理剖析(三)--加载配置文件

下面我们正式进入mybatis的源码学习&#xff0c;之前我们已经了解过mybatis中通过配置文件来保证与数据库的交互。配置文件分为核心配置文件和映射配置文件&#xff0c;核心配置文件的主要作用就是加载数据库的一些配置信息而映射配置文件则是执行对应的sql语句。同时核心配置文…

C++(运算符重载)

一.友元 C中使用关键字friend可以在类外访问所有的成员&#xff0c;包括私有成员&#xff08;之前提到过封装的核心思想是隐藏内部实现细节&#xff0c;通过公共接口控制访问&#xff09;&#xff0c;所以友元可以突破封装的限制访问数据&#xff0c;盲目使用会导致程序稳定性…

XR-RokidAR-UXR3.0-Draggable 脚本解析

using System.Collections.Generic; using Rokid.UXR.Utility; using UnityEngine; using UnityEngine.EventSystems;namespace Rokid.UXR.Interaction {/// <summary>/// Draggable 拖拽组件/// </summary>// [RequireComponent(typeof(RayInteractable))]public …

GitHub 趋势日报 (2025年06月17日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 1022 anthropic-cookbook 986 awesome-llm-apps 910 fluentui-system-icons 754 r…

NodeJS的中间件是什么

说简单一点&#xff0c;中间件就是在你的请求和业务逻辑之间做一层拦截。 在 Node.js 中&#xff0c;中间件&#xff08;Middleware&#xff09; 是一种函数&#xff0c;它在 请求&#xff08;Request&#xff09;到达路由处理器之前&#xff0c;或在 响应&#xff08;Respons…

MCAL学习(6)——诊断、DCM

1.诊断概述 汽车诊断就是通过汽车总线&#xff08;CAN LIN Eth&#xff09;来进行诊断会话&#xff0c;大部分通过CAN总线通讯进行请求与响应。 1.诊断分层 DCM内部支持UDS服务和OBD服务&#xff08;排放&#xff0c;动力&#xff09;。 以统一诊断服务UDS为例&#xff0c;应…

kafka-生产者-(day-4)

day-3 BufferPool 产生原因&#xff1a;ByteBuffer的创建和释放都是比较耗费资源的&#xff0c;为了实现内存的高效利用&#xff0c;产生了他。他会对特定大小的ByteBuffer进行管理 BufferPool的字段 free:是一个ArrayDeque队列&#xff0c;缓存指定大小的ByteBuffer对象Re…

java 验证ip是否可达

默认IP的设备已开放ping功能 代码 public class PingTest {public static void main(String[] args) throws Exception {String ip "192.168.21.101";boolean reachable InetAddress.getByName(ip).isReachable(3000);System.out.println(ip (reachable ? &quo…

LeetCode 2187.完成旅途的最少时间

题目&#xff1a; 给你一个数组 time &#xff0c;其中 time[i] 表示第 i 辆公交车完成 一趟旅途 所需要花费的时间。 每辆公交车可以 连续 完成多趟旅途&#xff0c;也就是说&#xff0c;一辆公交车当前旅途完成后&#xff0c;可以 立马开始 下一趟旅途。每辆公交车 独立 运…

永磁同步电机无速度算法--基于正切函数锁相环的滑模观测器

最近在学习锁相环&#xff0c;后续会记录一下了解到的几种PLL。 一、原理介绍 传统锁相环控制框图如下所示 在电机正转时&#xff0c;传统锁相环可以实现很好的转速和转子位置估计&#xff0c;但是当电机反转&#xff0c;反电动势符号发生变化&#xff0c;系统估计转子位置最…

Vim-vimrc 快捷键映射

Vim-vimrc 快捷键映射 文章目录 Vim-vimrc 快捷键映射Leader 键快捷键映射&#xff1a;插入特定字符插入 --插入 ##插入 解释Leader键设置快速插入分隔线 Leader 键 我们还将 , 设置为 Leader 键&#xff0c;使得其他快捷键映射更加简洁。 let mapleader ","快捷键…

SylixOS armv7 任务切换

SylixOS 操作系统下&#xff0c;任务切换可以分为两种 中断退出时&#xff0c;执行的任务切换&#xff08;_ScheduleInt&#xff09;内核退出时&#xff0c;执行的任务切换&#xff08;_Schedule&#xff09; 下面分别讲讲这两种任务切换 1、中断退出时任务切换 关于 ARM 架…

Java 自定义异常:如何优雅地处理程序中的“业务病”?

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、从一个真实场景开始&#xff1a;银行转账系统的困境 假设你正在开发一个银行转账系统&#xff0c;当用户尝试转账时可能出现以下问题&#xff1a; 转…

【JAVA】【Stream流】

1. filter操作 filter()方法用于根据给定的条件过滤列表中的元素&#xff0c;仅保留满足条件的项。 List<Integer> list Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);List<Integer> res list.stream().filter(a -> a % 2 0).collect(Collectors.toList());for(I…

四、Redis实现限流

简介&#xff1a; 限流算法在分布式领域是一个经常被提起的话题&#xff0c;当系统的处理能力有限时&#xff0c;如何阻止计划外的请求继续对系统施压。 系统要限定用户的某个行为在指定的时间里只能允许发生 N 次&#xff0c;如何使用 Redis 的数据结构来实现这个限流的功能&a…

基于Geotools的两条道路相交并根据交点形成新路线实战-以OSM数据为例

目录 前言 一、需求场景及分解 1、需求场景 2、需求应用 二、需求实现 1、加载路网数据 2、获取道路信息 3、相交点求解 4、生成新道路 5、结果可视化 三、总结 前言 在当今数字化迅速发展的时代&#xff0c;地理空间数据的处理与分析已成为众多领域不可或缺的关键技…

goland有基础速通(需要其它编程语言基础)

tip: 无论是变量、方法还是struct的访问权限控制都是通过命名控制的&#xff0c;命名的首字母是大写就相当于java中的public&#xff0c;小写的话就是private&#xff0c;&#xff08;private只有本包可以访问&#xff09; 1 go的变量声明 普通变量 特点&#xff1a; 变量类…

量化面试绿皮书:19. 相关系数

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 19. 相关系数 假设有三个随机变量x、y和z。 x与y之间的相关系数为0.8&#xff0c;x与z之间的相关系数也是0.8。 Q: 那么y与z之间的最大相关…

新生活的开启:从 Trae AI 离开后的三个月

很久没有写文章了&#xff0c;想借着入职新公司一个月的机会&#xff0c;和大家唠唠嗑。 离职 今年2月份我从字节离职了&#xff0c;结束了四年的经历&#xff0c;当时离开的核心原因是觉得加班时间太长了&#xff0c;平均每天都要工作15&#xff0c;16个小时&#xff0c;周末…

LLM部署之vllm vs deepspeed

部署大语言模型(如 Qwen/LLaMA 等)时,vLLM 与 DeepSpeed 是当前主流的两种高性能推理引擎。它们各自专注于不同方向,部署流程也有明显区别。 vLLM 提供极致吞吐、低延迟的推理服务,适用于在线部署;DeepSpeed 更侧重训练与推理混合优化,支持模型并行,适用于推理 + 微调/…