news 2026/8/11 0:31:37

Mysql中慢 SQL 优化实战:减少不必要的 JOIN 关联

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mysql中慢 SQL 优化实战:减少不必要的 JOIN 关联

Mysql中慢 SQL 优化实战:减少不必要的 JOIN 关联

一、问题背景

线上接口查询收货单的出库物流信息时出现慢 SQL。该 SQL 通过三表 JOIN(收货单主表 → 物流记录表 → 物流节点明细表)查询物流轨迹数据,其中收货单主表数据量超过 1200 万行。

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、优化流程

1. 定位慢 SQL

通过慢查询日志或 APM 监控系统,获取到慢 SQL 的完整文本、执行时间和调用链路(trace-id)。

2. 分析 SQL 结构

原始 SQL 的执行结构:

receiving_goods_record (主表, 1200w+) INNER JOIN store_receiving_logistics_record (物流记录表) LEFT JOIN store_receiving_logistics_record_item (物流节点明细表)

3. 识别冗余 JOIN

sql 中直接可以从表中获取数据,并且主表中的 record_code 字段是有索引的。分析发现 SELECT 中从主表取的字段(record_codeorder_code)在物流记录表中同样存在且数据一致,关联主表纯属冗余。

4. 数据一致性验证

通过 SQL 查询验证两表字段的数据一致性(不一致记录数 = 0),确认去掉 JOIN 不会导致数据差异。

5. 修改并测试

修改 Mapper XML,去掉冗余 JOIN,本地启动服务验证接口正常返回。

三、核心技术点

3.1 EXPLAIN 执行计划

EXPLAINSELECT...FROMtable_a aINNERJOINtable_b bONa.id=b.a_idWHEREa.code='xxx';

关键指标:

字段含义关注点
type访问类型ALL(全表扫描) → ref(索引) → const(常量)
key实际使用的索引NULL 表示未走索引
rows预估扫描行数数值越小越好
Extra额外信息Using filesort / Using temporary 需关注

3.2 索引命中原则

-- 单列索引:WHERE 条件精确匹配时命中CREATEINDEXidx_record_codeONlogistics_record(record_code);SELECT*FROMlogistics_recordWHERErecord_code='xxx';-- 命中-- 联合索引:遵循最左前缀原则CREATEINDEXidx_member_codeONlogistics_record(member_id,order_code);SELECT*FROMlogistics_recordWHEREmember_id=1;-- 命中SELECT*FROMlogistics_recordWHEREmember_id=1ANDorder_code='x';-- 命中SELECT*FROMlogistics_recordWHEREorder_code='x';-- 不命中

3.3 JOIN 的代价

每增加一个 JOIN,MySQL 需要:

  1. 根据驱动表的结果集逐行去被驱动表查找匹配行
  2. 如果被驱动表没有合适索引,会产生全表扫描
  3. 结果集膨胀 → 内存消耗增大 → 排序代价增大

四、优化原理

4.1 减少 JOIN 表数量

这是本次优化的核心原理。当你发现:

SELECT 中从 A 表取的字段,在 B 表中也有相同的数据(冗余存储/数据同步字段)

就可以去掉对 A 表的关联,直接从 B 表取字段。

优化前(三表)

SELECTa.name,b.detail,c.itemFROMbig_table a-- 1000w 行INNERJOINmedium_table bONa.id=b.a_idLEFTJOINsmall_table cONb.id=c.b_idWHEREa.code='xxx';

优化后(两表)

SELECTb.name,b.detail,c.item-- name 字段从 b 表直接取FROMmedium_table bLEFTJOINsmall_table cONb.id=c.b_idWHEREb.code='xxx';-- b 表的 code 有索引

4.2 驱动表选择

MySQL 的 JOIN 执行顺序由优化器决定,但通常:

  • 小表驱动大表性能更优
  • WHERE 条件能快速过滤的表适合作为驱动表

五、常见的慢 SQL 优化方案

方案一:消除冗余 JOIN(本次使用)

适用场景:JOIN 的表只是为了取几个字段,而这些字段在其他已关联的表中也有。

-- 优化前:三表 JOIN 只为取 order 表的 order_noSELECTo.order_no,d.product_name,d.qtyFROMorders oINNERJOINorder_details dONo.id=d.order_idWHEREo.id=12345;-- 优化后:order_details 表本身冗余存储了 order_noSELECTd.order_no,d.product_name,d.qtyFROMorder_details dWHEREd.order_id=12345;

方案二:添加合适索引

适用场景:WHERE 条件或 JOIN 条件的字段没有索引。

-- 慢查询:order_code 无索引,全表扫描SELECT*FROMshipment_detailWHEREmember_id=1179109ANDorder_code='ER.240418.000673';-- 添加联合索引CREATEINDEXidx_member_orderONshipment_detail(member_id,order_code);

方案三:改写子查询为 JOIN

适用场景:IN 子查询在大数据量下性能差。

-- 优化前:子查询SELECT*FROMordersWHEREcustomer_idIN(SELECTidFROMcustomersWHERElevel='VIP');-- 优化后:改写为 JOINSELECTo.*FROMorders oINNERJOINcustomers cONo.customer_id=c.idWHEREc.level='VIP';

方案四:分页优化(延迟关联)

适用场景:深分页 LIMIT offset, size 越往后越慢。

-- 优化前:LIMIT 100000, 10 需要扫描 100010 行SELECT*FROMordersORDERBYidDESCLIMIT100000,10;-- 优化后:先定位 ID 区间,再取数据SELECT*FROMordersWHEREid>=(SELECTidFROMordersORDERBYidDESCLIMIT100000,1)ORDERBYidDESCLIMIT10;

方案五:大表数据归档

适用场景:因为某表数据越来越多,导致执行 sql 越来越慢,因为 sql 暂时无法优化,所以先按照备份数据的方式处理,把指定之间之前的数据全部备份到备份表中,一年一张表。

-- 按年归档:将历史数据迁移到归档表INSERTINTOxxx_2025SELECT*FROMxxxWHEREaccount_time<'2026-01';DELETEFROMxxxWHEREaccount_time<'2026-01';-- 或使用 pt-archiver 等工具无锁归档

方案六:为大表新增索引(Online DDL)

适用场景:因为只使用创建时间查询数据,所以需要增加无锁变更,把 create_time 增加索引,表数据共 5000w。

-- MySQL 5.6+ 支持 Online DDL,不阻塞 DMLALTERTABLEstore_inbound_masterADDINDEXidx_create_time(create_time),ALGORITHM=INPLACE,LOCK=NONE;-- 或使用 gh-ost / pt-online-schema-change 进行无锁变更

六、优化效果评估

维度优化前优化后
JOIN 表数量3 表2 表
驱动表数据量1200w+ (receiving_goods_record)470w (store_receiving_logistics_record)
索引使用orm.record_code 走唯一索引后再 JOINsdlr.record_code 直接走索引
网络/内存开销需传输主表行数据无冗余数据传输

七、总结检查清单

在遇到慢 SQL 时,按以下顺序排查:

  1. 是否有不必要的 JOIN?— 分析 SELECT 字段来源,确认是否可以从已有表获取
  2. WHERE/JOIN 条件是否走索引?— 用 EXPLAIN 确认,必要时添加索引
  3. 是否有子查询可改写?— IN 子查询改为 JOIN
  4. 结果集是否过大?— 增加过滤条件、分页优化
  5. 表数据量是否可以缩减?— 历史数据归档、分表
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/11 0:26:32

别人用AI没事,你用就被抓?专科论文避坑5条铁律

别人用AI没事&#xff0c;你用就被抓&#xff1f;专科论文避坑5条铁律室友用AI一天写完了专科毕业论文初稿&#xff0c;查重一次过。你也用AI写&#xff0c;交上去被退回——AI率37%&#xff0c;超了红线。 你懵了。用的都是同一个工具&#xff0c;差别在哪&#xff1f; 2026年…

作者头像 李华
网站建设 2026/8/11 0:24:12

Cocos Creator 3.x粒子特效点击触发:从对象池到坐标转换的完整实现

1. 项目概述&#xff1a;从“播放”到“触发”的交互升级在 Cocos Creator 3.x 的项目开发中&#xff0c;粒子特效是营造视觉冲击力、提升游戏沉浸感的核心手段之一。我们通常习惯于在编辑器里摆好特效&#xff0c;设置好自动播放&#xff0c;或者用几行代码控制它的播放与停止…

作者头像 李华
网站建设 2026/8/10 23:59:34

Mac上使用UTM运行ROS Noetic的完整指南

1. 项目概述&#xff1a;为什么选择UTM在Mac上运行ROS Noetic&#xff1f; 在机器人开发领域&#xff0c;ROS&#xff08;Robot Operating System&#xff09;是事实上的标准框架&#xff0c;而Noetic作为最后一个支持Ubuntu 20.04的LTS版本&#xff0c;至今仍是许多工业项目的…

作者头像 李华
网站建设 2026/8/10 23:58:27

开源剪贴板管理工具全解析:从原理到企业级部署

1. 为什么你需要一个剪贴板管理工具&#xff1f; 在日常工作中&#xff0c;我经常遇到这样的场景&#xff1a;刚复制了一段重要代码&#xff0c;转头就被新的复制操作覆盖&#xff1b;或者需要反复在不同窗口间复制粘贴相同内容&#xff1b;甚至更糟的是&#xff0c;不小心关闭…

作者头像 李华
网站建设 2026/8/10 23:57:35

Python模块执行机制与__name__变量详解

1. Python模块执行机制解析 在Python开发中&#xff0c;每个.py文件都可以被视为一个模块。当Python解释器读取一个源文件时&#xff0c;它会执行该文件中所有的代码。在这个过程中&#xff0c;解释器会先定义一些特殊的变量&#xff0c;其中 __name__ 就是最常用的一个。这个…

作者头像 李华
网站建设 2026/8/10 23:55:40

SpringBoot2+Vue3+MySQL智慧康养服务平台源码 前后端分离实战

一、项目简介 智慧康养服务平台是一套面向养老机构的数字化管理系统&#xff0c;采用 Spring Boot 2 Vue 3 的前后端分离架构&#xff0c;覆盖老人入住管理、健康监护、膳食管理、费用结算、亲情联动等核心业务场景。系统内置普通用户端&#xff08;老人/监护人&#xff09;、…

作者头像 李华