小集合 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.mzph.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;手动扒漏洞文章…