MySQL多表JOIN聚合磁盘溢出?分批聚合实战:某教育平台50万行数据从崩溃到稳定
作者的话一、事故背景某教育集团的数据中台需要从DW层聚合生成6张DM层指标表。其中最大的4张表表名说明最终行数聚合方式dm_campus_subject_fail_rate校区学科不及格率约5万行三表JOIN GROUP BYdm_campus_subject_avg_score校区学科平均分约2.6万行三表JOIN GROUP BYdm_national_subject_avg_score全国学科平均分约2.1万行两表JOIN GROUP BYdm_national_subject_fail_rate全国学科不及格率约1500行两表JOIN GROUP BY聚合SQL长这样INSERTINTOdm_campus_subject_fail_rate(...)SELECTs.subject_type,s.campus_id,s.campus_name,t.exam_id,t.exam_name,COUNT(DISTINCTs.student_id)AStotal_students,COUNT(DISTINCTCASEWHENs.score_statusNOTIN(pass,exempt)THENs.student_idELSENULLEND)ASfail_students,ROUND(...)ASfail_rate,NOW(),NOW(),NOW()FROMdw_student sLEFTJOINdw_enrollment rONs.student_idr.student_idLEFTJOINdw_exam_score tONs.student_idt.student_idWHEREs.is_deleted0ANDr.is_deleted0ANDt.is_deleted0GROUPBYs.subject_type,s.campus_id,s.campus_name,t.exam_id,t.exam_name;三张DW层表JOIN后GROUP BY数据量约50万行。结果MySQL直接把RDS磁盘打满小规格实例磁盘仅50GB实例卡死同步任务崩溃。二、为什么临时表会炸2.1 MySQL临时表机制MySQL在执行GROUP BY时如果内存放不下超过tmp_table_size会把中间结果写到磁盘上的临时文件。三表JOIN GROUP BY的执行过程Step 1: dw_student JOIN dw_enrollment → 中间结果A约30万行 Step 2: 中间结果A JOIN dw_exam_score → 中间结果B约50万行 Step 3: 中间结果B GROUP BY → 临时表C磁盘上 Step 4: 临时表C INSERT INTO dm表中间结果B有50万行在小规格RDS上临时表可能占满剩余磁盘空间。2.2 RDS磁盘监控磁盘使用率96.2% ← 接近满载 IOPS接近上限 CPU100% MySQL状态Waiting for disk space三、优化尝试失败篇3.1 尝试1调大tmp_table_sizeSETSESSIONtmp_table_size1073741824;-- 1GBSETSESSIONmax_heap_table_size1073741824;-- 1GB结果小规格RDS内存有限1GB的内存临时表放不下50万行中间结果还是溢出到磁盘。3.2 尝试2优化SQL写法用子查询预先过滤INSERTINTOdm_campus_subject_fail_rate(...)SELECT...FROM(SELECTstudent_id,subject_type,campus_id,campus_name,score_statusFROMdw_studentWHEREis_deleted0)sINNERJOIN(SELECTstudent_idFROMdw_enrollmentWHEREis_deleted0)rONs.student_idr.student_idINNERJOIN(SELECTstudent_id,exam_id,exam_nameFROMdw_exam_scoreWHEREis_deleted0)tONs.student_idt.student_idGROUPBY...;结果MySQL优化器并不总是按子查询顺序执行执行计划可能还是全表扫描。临时表依然巨大。四、最终方案分批聚合4.1 核心思路不要一次性聚合所有数据而是按维度分批聚合。原始SQL是一次性聚合所有校区学科组合改为每次只聚合一个校区或一个分类组合的数据循环执行直到覆盖所有维度。4.2 实现代码defexecute_dm_batch_aggregation(conn,config):cursorconn.cursor()cursor.execute( SELECT DISTINCT subject_type, campus_id, campus_name FROM dw_student WHERE is_deleted 0 )campusescursor.fetchall()logger.info(f总校区数:{len(campuses)})execute_sql(conn,TRUNCATE TABLE dm_campus_subject_fail_rate)total_rows0batch_size10fori,campusinenumerate(campuses):connreconnect_if_needed(conn,config)sql INSERT INTO dm_campus_subject_fail_rate (...) SELECT %s, %s, %s, t.exam_id, t.exam_name, COUNT(DISTINCT s.student_id), COUNT(DISTINCT CASE WHEN s.score_status NOT IN (pass, exempt) THEN s.student_id ELSE NULL END), ROUND(...), NOW(), NOW(), NOW() FROM dw_student s LEFT JOIN dw_enrollment r ON s.student_id r.student_id LEFT JOIN dw_exam_score t ON s.student_id t.student_id WHERE s.is_deleted 0 AND r.is_deleted 0 AND t.is_deleted 0 AND s.subject_type %s AND s.campus_id %s GROUP BY t.exam_id, t.exam_name params(campus[subject_type],campus[campus_id],campus[campus_name],campus[subject_type],campus[campus_id])rowsexecute_sql(conn,sql,params,configconfig)total_rowsrowsif(i1)%batch_size0:release_temp_tables(conn)logger.info(f进度:{i1}/{len(campuses)}, 已插入{total_rows}行)gc.collect()cursor.close()deletedexecute_sql(conn,DELETE FROM dm_campus_subject_fail_rate WHERE total_students 50)release_temp_tables(conn)4.3 为什么分批聚合有效对比项一次性聚合分批聚合单次SQL处理数据量50万行约5000-1万行临时表大小约5GB约50MB磁盘占用峰值接近100%15%执行时间崩溃无法完成约20-30分钟失败恢复从头来失败批次可重试五、配套优化措施5.1 Session参数优化defoptimize_session(conn):withconn.cursor()ascursor:cursor.execute(SET SESSION tmp_table_size 1073741824)# 1GBcursor.execute(SET SESSION max_heap_table_size 1073741824)# 1GBcursor.execute(SET SESSION sort_buffer_size 268435456)# 256MBcursor.execute(SET SESSION join_buffer_size 268435456)# 256MBconn.commit()5.2 主动释放临时表defrelease_temp_tables(conn):try:withconn.cursor()ascursor:cursor.execute(FLUSH TABLES)conn.commit()except:passFLUSH TABLES会强制MySQL关闭所有不再使用的临时表立即释放磁盘空间。5.3 Python垃圾回收importgcif(i1)%batch_size0:gc.collect()5.4 连接保活与重连defreconnect_if_needed(conn,config):try:withconn.cursor()ascursor:cursor.execute(SELECT 1)returnconnexcept:returnpymysql.connect(**config)六、效果对比指标优化前优化后同步成功率0%必崩99%RDS磁盘峰值96%25%单次同步耗时无法完成约20-30分钟失败恢复从头来可从失败批次继续运维介入频率每次需人工处理基本无需介入七、总结多表聚合磁盘溢出的根因是一次性处理的数据量超过了MySQL临时表的承载能力。分批聚合的本质是把一个大问题拆成N个小问题每个小问题都在MySQL可控范围内。分批聚合的三个关键点选择合适的分批维度按分类维度如校区、科室、学科分批确保每批数据量均匀每批后释放资源FLUSH TABLESgc.collect()防止资源累积连接保活长时间执行必须处理连接断开的情况一句话总结当MySQL告诉你磁盘满了不是让你加磁盘而是让你把一个大SQL拆成N个小SQL。分批聚合大道至简。这篇文章如果帮到了你点赞收藏是对作者最大的支持有多表聚合经验的同学评论区分享你的方案~

相关新闻

如何快速部署ChatLaw:完整的开源中文法律AI助手搭建指南

如何快速部署ChatLaw:完整的开源中文法律AI助手搭建指南

如何快速部署ChatLaw:完整的开源中文法律AI助手搭建指南 【免费下载链接】ChatLaw ChatLaw:A Powerful LLM Tailored for Chinese Legal. 中文法律大模型 项目地址: https://gitcode.com/gh_mirrors/ch/ChatLaw 在当今数字化时代,获取…

2026/6/25 14:28:14阅读更多 →
MC9S08SE8中断与看门狗实战:从寄存器配置到系统稳定设计

MC9S08SE8中断与看门狗实战:从寄存器配置到系统稳定设计

1. 项目概述在嵌入式开发的世界里,中断和看门狗定时器是确保系统稳定、可靠、实时响应的两大基石。无论你是在设计一个智能家居的温控器,还是一个工业现场的电机控制器,只要涉及到与外部世界的交互或对异常状态的监控,这两项技术就…

2026/6/25 14:28:14阅读更多 →
Claude / Cursor 接入 API 常见报错与完整解决方案(新手避坑)

Claude / Cursor 接入 API 常见报错与完整解决方案(新手避坑)

最近 AI 编程工具火得一塌糊涂,尤其是 Cursor 加上 Claude 模型的组合,简直是写代码的“物理外挂”。但很多新手在刚上手配置 API 时,往往还没开始爽,就被满屏的报错劝退了。作为一个踩过无数坑的过来人,我花了几天时间…

2026/6/25 14:28:14阅读更多 →
戴森V6/V7电池开源固件升级完全指南:解锁隐藏的电芯平衡功能

戴森V6/V7电池开源固件升级完全指南:解锁隐藏的电芯平衡功能

戴森V6/V7电池开源固件升级完全指南:解锁隐藏的电芯平衡功能 【免费下载链接】FU-Dyson-BMS (Unofficial) Firmware Upgrade for Dyson V6/V7 Vacuum Battery Management System 项目地址: https://gitcode.com/gh_mirrors/fu/FU-Dyson-BMS 还在为戴森吸尘器…

2026/6/25 15:59:41阅读更多 →
Syncthing终极部署指南:三步构建你的私有同步网络

Syncthing终极部署指南:三步构建你的私有同步网络

Syncthing终极部署指南:三步构建你的私有同步网络 【免费下载链接】syncthing Open Source Continuous File Synchronization 项目地址: https://gitcode.com/GitHub_Trending/sy/syncthing 你是否厌倦了云存储的隐私泄露风险?是否受够了网盘的速…

2026/6/25 15:59:41阅读更多 →
Triton推理服务实战:从模型加载到灰度发布的生产级落地

Triton推理服务实战:从模型加载到灰度发布的生产级落地

1. 项目概述:这不是一次“部署上线”,而是一场从实验室到产线的系统性迁移 “From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着一个被无数团队反复验证、又反复踩坑的真相: 把 Jupyter 里跑通的模型…

2026/6/25 15:59:41阅读更多 →
使用Thead子类创建线程和使用Thread直接创建线程(Runnable接口)的区别?

使用Thead子类创建线程和使用Thread直接创建线程(Runnable接口)的区别?

class TicketThread extends Thread {private int ticket 10;Overridepublic void run() {while(ticket > 0) {System.out.println(getName() "卖票:" ticket--);}} } public class ThreadDemo {public static void main(String[] args) {// 两个独…

2026/6/25 15:59:41阅读更多 →
OpCore-Simplify终极指南:5步实现Hackintosh EFI配置自动化,成功率提升至92%

OpCore-Simplify终极指南:5步实现Hackintosh EFI配置自动化,成功率提升至92%

OpCore-Simplify终极指南:5步实现Hackintosh EFI配置自动化,成功率提升至92% 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify O…

2026/6/25 15:59:41阅读更多 →
深度解析STS-Bcut:基于必剪API的自动化语音转字幕实战指南

深度解析STS-Bcut:基于必剪API的自动化语音转字幕实战指南

深度解析STS-Bcut:基于必剪API的自动化语音转字幕实战指南 【免费下载链接】STS-Bcut 使用必剪API,语音转字幕,支持输入声音文件,也支持输入视频文件自动提取音频。 项目地址: https://gitcode.com/gh_mirrors/st/STS-Bcut …

2026/6/25 15:54:39阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/25 9:39:54阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/25 2:52:24阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/25 9:01:34阅读更多 →
面试辅助工具横评:我试了5款AI面试工具,最后留下了OfferGo

面试辅助工具横评:我试了5款AI面试工具,最后留下了OfferGo

上半年跳槽,面了十几家公司。说句实话,不是能力不行,是面试现场太容易崩了。 明明准备了一周,面试官换个问法脑子就一片白。面完之后那个懊悔——其实我会的。 后来开始试市面上的AI面试辅助工具。前前后后装了5款,踩…

2026/6/25 11:52:11阅读更多 →
Claude Code 提示词设计:从塑造“人格”到建立“状态机”

Claude Code 提示词设计:从塑造“人格”到建立“状态机”

当前 AI Agent 设计的核心痛点在于:大模型不缺写代码的能力,缺的是克制力、边界感和验证逻辑。Prompt 不再是用来塑造“人格”的,而是用来建立“状态机(State Machine)”和“行为门禁(Guardrails&#xff0…

2026/6/25 11:52:11阅读更多 →
MC-037 | 自定义 Skill 开发:创建你的AI能力模块

MC-037 | 自定义 Skill 开发:创建你的AI能力模块

MONKEYCODE 教程系列 MonkeyCode教程及推广系列 MC-037 自定义 Skill 开发:创建你的AI能力模块 >官网链接注册更放心哦https://monkeycode-ai.com/?ic019e0aed-c823-783c-b08a-4f030f891e4e 系列: 不爱土豆唯爱马铃薯 MonkeyCode 教程系列 字数: 约 1400 字…

2026/6/25 11:52:11阅读更多 →