分頁數據不準問題分析與解決

大綱 📖

  • 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
英文地址,請注明出處:http://en.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 更側重訓練與推理混合優化,支持模型并行,適用于推理 + 微調/…