小集合 VS 大集合:MySQL 去重計數性能優化

小集合 VS 大集合:MySQL 去重計數性能優化

  • 前言
  • 一、場景與問題 🔎
  • 二、通俗執行流程對比
  • 三、MySQL 執行計劃解析 📊
  • 四、性能瓶頸深度剖析 🔍
  • 五、終極優化方案 🏆
  • 六、總結


前言

📈 測試結果

在對百萬級 indicator_log 表進行 去重計數 的測試中,我們發現:

  • SQL1(先去重再計數)耗時 ≈ 0.9s,

  • SQL2(直接 COUNT(DISTINCT))耗時 ≈ 1.0s。

🔍 核心原因

  • SQL1 利用物化臨時表批量去重,I/O 可控;

  • SQL2 在內存哈希/排序中實時去重,內存與 CPU 負載更重,并觸發更多 spill-to-disk 。

最終,通過覆蓋式聯合索引、內存參數調優及Loose Index Scan等手段,能讓兩者在大數據量下都達到毫秒級。

一、場景與問題 🔎

  • 表結構示例(示例參數)

    CREATE TABLE indicator_log (obj_id      INT,  -- 評估對象 ID (:obj_id)plan_id     INT,  -- 評估計劃 ID (:plan_id)del_flag    TINYINT,  -- 邏輯刪除標志 (:del_flag)INDEX idx_plan    (plan_id), -- 單獨索引 (:plan_id)INDEX idx_delflag (del_flag) -- 單獨索引 (:del_flag)
    );
    
  • 需求:統計某評估計劃中、未被邏輯刪除的唯一對象數。

  • SQL1(子查詢版)

    SELECT COUNT(obj_id)
    FROM (SELECT DISTINCT obj_idFROM indicator_logWHERE plan_id = 312 AND del_flag = 0
    ) AS t;
    
  • SQL2(直接版)

    SELECT COUNT(DISTINCT obj_id)
    FROM indicator_log
    WHERE plan_id = 312 AND del_flag = 0;
    

二、通俗執行流程對比

  1. SQL1:階段化去重

    • 子查詢去重

      SELECT DISTINCT obj_id
      FROM indicator_log
      WHERE plan_id = :plan_id  AND del_flag = :del_flag;
      
      • ?? 數據庫先從大表中抽取所有唯一obj_id,并將結果寫入“小籃子”(物化臨時表),

      • 此階段只做一次去重,借助外部排序分區哈希批量處理,I/O 可控、穩定

    • 外層快速計數

      SELECT COUNT(obj_id)
      FROM (… 上一步子查詢 …
      ) AS t;
      
      • ? 在“小籃子”上做 COUNT,不涉及任何去重邏輯,

      • 僅需對已去重的小結果集掃描一次,CPU 和 I/O 開銷極低

    優勢:先縮小數據規模,再聚合,適合大數據量場景。

  2. SQL2:一次性去重

    SELECT COUNT(DISTINCT obj_id)
    FROM indicator_log
    WHERE plan_id = :plan_id  AND del_flag = :del_flag;
    
    • 實時掃描去重

      • 🏃 MySQL 在全表掃描過程中,邊讀取每行邊將 obj_id 插入內存哈希表或進行內存排序

      • 每次插入都需判斷是否已存在,CPU 和內存壓力陡增

    • 礦山級 Hash / 排序

      • 🔄 若待去重行數超過 sort_buffer_sizetmp_table_size,會頻繁 spill-to-disk

      • 導致磁盤 I/O 大幅增加,性能抖動明顯

    劣勢:一次性完成去重+計數,對內存依賴高,遇大數據量易觸發磁盤溢寫。

  3. 索引合并(Index Merge)附加開銷 ??

    • 在只有單列索引 idx_plan(plan_id)idx_delflag(del_flag) 時,MySQL 必須:

      • 分別走兩個索引掃描;

      • 對掃描結果做行號交集Index Merge Intersection) ;

    • 雙重掃描 + 交集 也為兩種寫法都增加了額外 I/O 和 CPU 消耗。

三、MySQL 執行計劃解析 📊

  • SQL1 的 EXPLAIN

    EXPLAIN ANALYZE
    SELECT COUNT(obj_id)
    FROM (SELECT DISTINCT obj_idFROM indicator_logWHERE plan_id = 312AND del_flag = 0
    ) AS t;
    

    在這里插入圖片描述

  • 執行計劃解析

    1. 聚合操作:計算 obj_id 的總數,執行成本和實際時間較低。

    2. 表掃描:查詢對 t 表進行了全表掃描,掃描了約 280,269 行,實際執行時間為 902 毫秒。

    3. 物化:將中間結果存儲在內存中,避免重復計算,時間與表掃描相同。

    4. 臨時表:查詢創建了臨時表進行去重,去重操作與物化時間相同。

    5. 過濾條件:通過 del_flag = 0plan_id = 312 過濾數據,執行時間較長,返回 165,849 行。

    6. 交集操作:從兩個索引掃描中交集數據,執行時間較長。

    7. 索引掃描

    • 使用 idx_plan 掃描符合 plan_id = 312 的數據,執行非常快。

    • 使用 idx_delflag 掃描符合 del_flag = 0 的數據,執行較慢,因為掃描了大量數據。

  • 總結

    1. Index Merge Intersection  ├─ idx_plan    (plan_id=:plan_id)  └─ idx_delflag (del_flag=:del_flag)  📚 :contentReference[oaicite:3]{index=3}  
    2. Temporary table with deduplication       📚 :contentReference[oaicite:4]{index=4}  
    3. Table scan on <temporary>  
    4. Aggregate: COUNT(obj_id)
    
    • 交集掃描:分別走兩個單列索引,再取交集,得到 N 條候選行

    • 物化去重:寫入臨時表后批量排序去重,I/O 可控

    • 快速計數:對臨時小表直接 COUNT,耗時極低。

    查詢的瓶頸主要在于對 del_flag 的過濾和交集操作,建議優化索引或減少數據量。

  • SQL2 的 EXPLAIN

    EXPLAIN ANALYZE
    SELECT COUNT(DISTINCT obj_id)
    FROM indicator_log
    WHERE plan_id = 312AND del_flag = 0;
    

    在這里插入圖片描述

  • 執行計劃解析

    1. 聚合操作count(distinct indicator_log.obj_id),計算 obj_id 的去重總數,執行成本和時間較低,實際執行時間為 964 毫秒。

    2. 過濾條件:查詢對 indicator_log 表進行了過濾,條件為 del_flag = 0plan_id = 312。過濾后返回了 165,849 行數據,執行時間為 341 到 838 毫秒。

    3. 交集操作:通過 INTERSECT 操作結合兩個索引掃描結果,篩選符合條件的數據。執行時間為 341 到 837 毫秒,結果包含 165,849 行。

    4. 索引掃描

    • 使用 idx_plan 索引掃描 plan_id = 312 的數據,執行非常快,時間為 0.148 到 85.3 毫秒,掃描了 279,786 行。

    • 使用 idx_delflag 索引掃描 del_flag = 0 的數據,執行較慢,時間為 0.051 到 426 毫秒,掃描了大約 1.5 百萬行。

  • 總結

    1. Index Merge Intersection  ├─ idx_plan  └─ idx_delflag  
    2. Filter predicates  
    3. Aggregate: COUNT(DISTINCT obj_id)  🔄  
    
    • 同樣交集得出 N 行;

    • 內存去重:逐行插入 HashSet 或排序,邊去重邊計數

    • 瓶頸:大量內存操作易觸發 spill-to-disk 或頻繁 GC,性能抖動明顯

    查詢主要瓶頸在于對 del_flag = 0 條件的過濾,因為這個條件掃描了大量數據。可以通過優化索引或減少數據量來提高查詢性能。

四、性能瓶頸深度剖析 🔍

  1. 索引合并(Index Merge)開銷
  • 單列索引需做兩次范圍掃描并交集,I/O 與 CPU 成本陡增 。
  • 覆蓋式聯合索引可一步到位,跳過合并與回表,大幅縮短掃描范圍 。
  1. 去重策略對比
特性臨時表批量去重 (SQL1)內存哈希/排序 (SQL2)
實現方式外部排序 + 臨時表 I/OHashSet/排序,內存優先
穩定性高(I/O 可控)受限于 tmp_table_size/sort_buffer_size
典型場景中大規模去重小數據量、快速響應
  1. I/O vs 內存權衡
  • SQL1:I/O 適當增加,換取穩定去重;

  • SQL2:依賴內存,當數據量超出配置時表現不穩 。

  1. 統計信息影響
  • 高選擇性 (plan_id) 與 低選擇性 (del_flag) 配合不當,容易讓優化器選錯計劃;

  • 保持準確統計信息,定期 ANALYZE TABLE 是必備流程 。

五、終極優化方案 🏆

  1. 覆蓋式聯合索引 ?

    CREATE INDEX idx_opt ON indicator_log(plan_id, del_flag, obj_id);
    
    • 一次掃描完成所有條件過濾plan_iddel_flag → 取出 obj_id,無需再做索引合并或回表

    • 支持索引覆蓋(Covering Index),減少磁盤 I/O,聚合與去重都可在索引層直接完成

  2. 內存與臨時表參數調優 🔧

    SET GLOBAL tmp_table_size        = 256M;
    SET GLOBAL max_heap_table_size   = 256M;
    SET GLOBAL sort_buffer_size      = 64M;
    
    • 增大內存閾值,讓大多數臨時表都在內存中完成,避免頻繁落盤

    • 提高排序緩沖區,減少 COUNT(DISTINCT)ORDER BY 時的 spill-to-disk

  3. 啟用 Loose Index Scan 🚀

    SET SESSION optimizer_switch = 'loose_index_scan=on';
    
    • 對于 COUNT(DISTINCT obj_id),MySQL 5.6+ 可以利用“松散索引掃描”

    • 在覆蓋索引場景下,只需依次跳讀不同值的第一條記錄,即可高效去重

  4. 物化視圖 / 預聚合表 🗄?

  • 寫時維護:在插入/更新階段,通過觸發器或應用邏輯同步維護 (plan_id, unique_obj_count)

  • 定時批處理:夜間或低峰期,將去重結果寫入專用聚合表,查詢時直接讀取,無需在線去重


六、總結

  • 🧺 SQL1 = 小集合計數

    先執行子查詢:SELECT DISTINCT obj_id …,把所有唯一值抽取到“小籃子”中(臨時表或物化表),然后再對這“小籃子”做 COUNT(obj_id)。拆分去重和計數兩步,使得 I/O 可控、壓力分散,性能更穩定 。

  • ? SQL2 = 大集合實時計數

    直接在大表上執行 COUNT(DISTINCT obj_id),MySQL 需要邊掃描邊在內存中維護哈希表或做外部排序來去重并計數。這種“一次性”實時去重對內存和 CPU 依賴極高,一旦超過內存閾值就會頻繁 spill-to-disk,性能抖動明顯 。

👉 真·性能優化,絕非單點發力,而是「SQL 寫法 + 執行計劃 + 索引設計 + 系統參數」四位一體,才能在海量數據面前保持高效穩定

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/80770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80770.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/80770.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

3、Linux操作系統下,linux的技術手冊使用(man)

linux系統內置技術手冊&#xff0c;方便開發人員查閱Linux相關指令&#xff0c;提升開發效率 man即是manual的前三個字母&#xff0c;有時候遇事不決&#xff0c;問個人&#xff08;man&#xff09; 其在線網址為&#xff1a;man 還有man網站的作者寫的書&#xff0c;可以下…

京東商品詳情數據爬取難度分析與解決方案

在當今數字化商業時代&#xff0c;電商數據對于市場分析、競品研究、價格監控等諸多領域有著不可估量的價值。京東&#xff0c;作為國內首屈一指的電商巨頭&#xff0c;其商品詳情頁蘊含著海量且極具價值的數據&#xff0c;涵蓋商品價格、庫存、規格、用戶評價等關鍵信息。然而…

正確應對監管部門的數據安全審查

首席數據官高鵬律師團隊編著 在當今數字化時代&#xff0c;數據安全已成為企業及各類組織面臨的重要議題&#xff0c;而監管部門的數據安全審查更是關乎其生存與發展的關鍵挑戰。隨著法律法規的不斷完善與監管力度的加強&#xff0c;如何妥善應對這一審查&#xff0c;避免潛在…

三星One UI安全漏洞:剪貼板數據明文存儲且永不過期

三星One UI系統曝出重大安全漏洞&#xff0c;通過剪貼板功能導致數百萬用戶的敏感信息面臨泄露風險。 剪貼板數據永久存儲 安全研究人員發現&#xff0c;運行Android 9及以上系統的三星設備會將所有剪貼板內容——包括密碼、銀行賬戶詳情和個人消息——以明文形式永久存儲&am…

動態規劃求解leetcode300.最長遞增子序列(LIS)詳解

給你一個整數數組 nums &#xff0c;找到其中最長嚴格遞增子序列的長度。 子序列 是由數組派生而來的序列&#xff0c;刪除&#xff08;或不刪除&#xff09;數組中的元素而不改變其余元素的順序。例如&#xff0c;[3,6,2,7] 是數組 [0,3,1,6,2,2,7] 的子序列。 示例 1&#…

Rule.resourceQuery(通過路徑參數指定loader匹配規則)

1. 說明 在 webpack 4 中&#xff0c;Rule.resourceQuery 是一個用于根據文件路徑中的 查詢參數&#xff08;query string&#xff09; 來匹配資源的配置項。它允許你針對帶有特定查詢條件的文件&#xff08;如 file.css?inline 或 image.png?raw&#xff09;應用不同的加載…

快速上手 MetaGPT

1. MetaGPT 簡介 在當下的大模型應用開發領域&#xff0c;Agent 無疑是最炙手可熱的方向&#xff0c;這也直接催生出了眾多的 Agent 開發框架。在這之中&#xff0c; MetaGPT 是成熟度最高、使用最廣泛的開發框架之一。 MetaGPT 是一款備受矚目的多智能體開發框架&#xff0c…

新聞數據接口開發指南:從多源聚合到NLP摘要生成

隨著人工智能&#xff08;AI&#xff09;技術的飛速發展&#xff0c;新聞行業也迎來了新的變革。AI不僅能夠自動化生成新聞內容&#xff0c;還能通過智能推薦系統為用戶提供個性化的新聞體驗。萬維易源提供的“新聞查詢”API接口&#xff0c;結合了最新的AI技術&#xff0c;為開…

每天五分鐘深度學習框架pytorch:使用visdom繪制損失函數圖像

visdom的安裝 pip install visdom如果安裝失敗 pip install --upgrade visdom開啟visdom python -m visdom.server nohup python -m visdom.server后臺啟動然后就會出現,下面的頁面,我們可以使用下面的鏈接打開visdom頁面 Visdom中有兩個重要概念: env環境。不同環境的可…

UnityEditor - 調用編輯器菜單功能

例如: 調用Edit/Frame Selected In Scene EditorApplication.ExecuteMenuItem("Edit/Frame Selected in Scene"); EditorApplication.ExecuteMenuItem("Edit/Lock view to Selected");

電化學-論文分享-NanoStat: An open source, fully wireless potentiostat

電化學-論文分享-NanoStat: An open source, fully wireless potentiostat 發現了一篇近期有關便攜式電化學工作站相關方面的論文&#xff08;2022&#xff09;&#xff0c;并且全部工作內容都是開源的&#xff0c;硬件電路圖、PCB板、嵌入式代碼以及網頁代碼、設備外殼所有資…

ZYNQ----------PS端入門(四)(根文件系統進emmc,鏡像和設備樹進flash)

文章目錄 系列文章目錄前言一、根文件系統是什么&#xff1f;二、根文件系統燒進emmc1.emmc是什么&#xff1f;2.根文件系統的位置3.分離根文件系統步驟1.14.分離根文件系統步驟1.25.分離根文件系統步驟2.1 三、根文件系統進emmc&#xff0c;設備樹和鏡像進flash 系列文章目錄 …

uniapp+vue3移動端實現輸入驗證碼

ios安卓 uniappvue3 微信小程序端 <template><view class"verification-code"><view class"verification-code__display"><block v-for"i in numberArr" :key"i"><view:class"[verification-code__d…

如何選擇游戲支付平臺呢?

如果要選擇一個游戲支付平臺的話&#xff0c;那么你可以考慮一下這個平臺&#xff1a;功能非常多&#xff0c;支付模式很高效&#xff0c;功能很全&#xff0c;服務很貼心&#xff0c;資金安全靠得住&#xff0c;安全認證模式也很可靠。 第二&#xff0c;結算方法也很多&#x…

前端如何獲取文件的 Hash 值?多種方式詳解、對比與實踐指南

文章目錄 前言一、Hash 值為何重要&#xff1f;二、Hash 值基礎知識2.1 什么是 Hash&#xff1f;2.2 Hash 在前端的應用場景2.3 常見的 Hash 算法&#xff08;MD5、SHA 系列&#xff09; 三、前端獲取文件 Hash 的常用方式3.1 使用 SparkMD5 計算 MD5 值3.2 使用 Web Crypto AP…

【Java學習筆記】類與對象

類與對象 什么是類&#xff1f; 知識遷移&#xff1a;類比 C 語言中的結構體 類的描述 類是一個對象的抽象&#xff0c;從字面意思就表示一個類的事物&#xff0c;類具有屬性和方法&#xff08;行為&#xff09;&#xff0c;對象是類的一個具體表現 總結&#xff1a;類是對象…

如何對極狐GitLab 議題進行過濾和排序?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 排序和議題列表排序 (BASIC ALL) 您可以通過多種方式對議題列表進行排序&#xff0c;可用的排序選項可以根據列表的上下文進…

k8s中資源的介紹及標準資源namespaces實踐

文章目錄 第1章 k8s中的資源(resources)介紹1.1 k8s中資源(resouces)的分類1.2 k8s中資源(resources)的級別1.3 k8s中資源(resources)的API規范1.4 k8s中資源(resources)的manifests 第2章 k8s中的標準資源之namespaces的實踐2.1 基本介紹2.2 編寫相關ns資源對象的manifests2.3…

優化uniappx頁面性能,處理頁面滑動卡頓問題

問題&#xff1a;在頁面遇到滑動特別卡的情況就是在頁面使用了動態樣式或者動態類&#xff0c;做切換的時候頁面重新渲染導致頁面滑動卡頓 解決&#xff1a;把動態樣式和動態類做的樣式切換改為通過獲取元素修改樣式屬性值 循環修改樣式示例 bannerList.forEach((_, index)…

DeepSeek賦能Nuclei:打造網絡安全檢測的“超級助手”

引言 各位少俠&#xff0c;周末快樂&#xff0c;幸會幸會&#xff01; 今天嘮一個超酷的技術組合——用AI大模型給Nuclei開掛&#xff0c;提升漏洞檢測能力&#xff01; 想象一下&#xff0c;當出現新漏洞時&#xff0c;少俠們經常需要根據Nuclei模板&#xff0c;手動扒漏洞文章…