MySQL的 JOIN 优化终极指南

在这里插入图片描述

目录

    • 前言
    • 序章:为何要有JOIN?——“一个好汉三个帮”的数据库哲学 🤝
    • 第一章:JOIN的“七十二变”——常见JOIN类型速览 🎭
    • 第二章:MySQL的“红娘秘籍”——JOIN执行原理大揭秘 🕵️‍♀️📖
      • 2.1 简单嵌套循环连接 (Simple Nested Loop Join, SNLJ) - “老实人的笨办法” 🐢
      • 2.2 索引嵌套循环连接 (Index Nested Loop Join, INLJ) - “聪明人的快捷方式” 🚀
      • 2.3 块嵌套循环连接 (Block Nested Loop Join, BNL) - “批量相亲,减少跑腿” 🚌
      • 2.4 MySQL 8.0+ 的新贵:哈希连接 (Hash Join) 🧙‍♂️
    • 第三章:JOIN优化的“葵花宝典”——核心法则与实战技巧 🚀📖
      • 1. 索引!索引!还是TMD索引!(最重要的事说三遍) 🔑🔑🔑
      • 2. 驱动表的选择:“谁先动筷子”的艺术 🥢
      • 3. 过滤条件要“给力”:尽可能早地减少结果集 📉
      • 4. join_buffer_size:不是万能丹,合理使用才有效 💊
      • 5. EXPLAIN:你的JOIN优化“导航仪” 🗺️
      • 6. MySQL 8.0+ 的其他JOIN优化特性 (锦上添花) 🌸
      • 7. JOIN查询的“七宗罪” (常见避坑指南) 🚫
    • 第四章:实战演练——看个例子压压惊 👨‍🏫
    • 总结:JOIN优化🧘‍♂️

🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!

其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等

如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning

前言

大家好!又双叒叕是我,你们的老朋友,一个幽默的程序员。

今天,咱们来点更刺激的,聊聊那个让无数英雄竞折腰的——JOIN查询优化

你是不是也写过那种“九九八十一难”般的JOIN语句,一执行,MySQL就跟便秘似的,半天憋不出一个P(结果)?或者,你看着EXPLAIN那一堆眼花缭乱的Nested LoopUsing join buffer,感觉智商被按在地上摩擦?

别慌!JOIN虽然复杂,但它不是“爱情魔咒”,只要你摸清了它的“脾气秉性”,掌握了正确的“撩妹技巧”(优化方法),它也能从“世纪大难题”变成你SQL工具箱里的“瑞士军刀”!

准备好了吗?系好安全带,咱们的“JOIN优化探索号”飞船,马上起航!目的地——高效JOIN的“幸福彼岸”!🚀💑

序章:为何要有JOIN?——“一个好汉三个帮”的数据库哲学 🤝

想象一下,你的数据被精心设计,分门别类地存放在不同的“小抽屉”(表)里。比如:

  • students表:存放学生的基本信息(学号、姓名、班级ID)。
  • classes表:存放班级信息(班级ID、班主任、教室)。
  • scores表:存放学生的考试成绩(学号、科目、分数)。

现在,你想知道“火箭班所有学生的姓名及其各科成绩”,单靠一个“抽屉”肯定搞不定吧?你得把studentsscores这两个抽屉打开,根据“学号”这个共同的线索,把它们关联起来。

JOIN,就是数据库世界里的“联谊会主持人”! 它的核心任务,就是根据你指定的“共同话题”(连接条件),把来自不同表(抽屉)的相关数据行“拉郎配”,组合成一个更完整、更有意义的结果集。

没有JOIN,数据就是一座座孤岛;有了JOIN,数据才能汇聚成汪洋大海,展现出真正的价值!

第一章:JOIN的“七十二变”——常见JOIN类型速览 🎭

在MySQL的“联谊会”上,主持人(JOIN)会根据你的要求,采用不同的“配对策略”。咱们先来快速认识一下几位常见的“联谊会司仪”:

  1. INNER JOIN (内连接):最严格的“司仪”,只介绍那些在两个表里都能找到“共同话题”(匹配连接条件)的行。如果A表的某行在B表找不到伴儿,或者B表的某行在A表找不到伴儿,对不起,它俩都不能参加这场“内涵派对”。

    • 口头禅:“宁缺毋滥,非诚勿扰!”
    • 写法SELECT ... FROM tableA INNER JOIN tableB ON tableA.col = tableB.col; (或者直接 SELECT ... FROM tableA, tableB WHERE tableA.col = tableB.col;,效果类似,但推荐显式JOIN)
  2. LEFT JOIN (左连接,也叫 LEFT OUTER JOIN):偏心眼的“司仪”,以左边的表(FROM子句中先出现的表)为准。左表的每一行都会出现在结果中。

    • 如果右表有匹配的行,就正常配对。
    • 如果右表没有匹配的行,右表相关的列会用NULL来填充,“强行配对,找不到对象就给你个空气伴侣”。
    • 口头禅:“左边的都是爷,一个都不能少!右边的?能配就配,配不上拉倒(用NULL)!”
    • 写法SELECT ... FROM tableA LEFT JOIN tableB ON tableA.col = tableB.col;
  3. RIGHT JOIN (右连接,也叫 RIGHT OUTER JOIN):跟LEFT JOIN反过来,以右边的表为准。

    • 口头禅:“右边的都是姑奶奶,全都得伺候好!左边的?随缘吧!”
    • 小技巧:很多时候,A RIGHT JOIN B 都可以改写成 B LEFT JOIN A,效果一样,但LEFT JOIN更常用,可读性可能更好。
  4. FULL JOIN (全连接,也叫 FULL OUTER JOIN):最大方的“司仪”,左边右边的客人一个都不落下!

    • 左表有匹配,右表有匹配:正常配对。
    • 左表有,右表没有:左表数据显示,右表数据为NULL
    • 左表没有,右表有:右表数据显示,左表数据为NULL
    • 口头禅:“来者都是客,一个都不能少!找不到伴儿的,我给你们发‘安慰奖’(NULL)!”
    • MySQL的“小遗憾”:MySQL本身不直接支持FULL OUTER JOIN关键字。但别灰心,你可以通过LEFT JOIN ... UNION ... RIGHT JOIN (或者 LEFT JOIN ... UNION ALL ... RIGHT JOIN WHERE A.key IS NULL 等变体) 来模拟实现全连接的效果。
      -- 模拟FULL JOIN (注意,对于匹配上的行会显示两次,如果想去重用UNION)
      SELECT * FROM tableA LEFT JOIN tableB ON tableA.id = tableB.id
      UNION ALL -- 或者 UNION 去重
      SELECT * FROM tableA RIGHT JOIN tableB ON tableA.id = tableB.id
      WHERE tableA.id IS NULL; -- 只取右表有而左表没有的部分
      
  5. CROSS JOIN (交叉连接,也叫笛卡尔积):最“疯狂”的“司仪”,不做任何筛选,把A表的每一行和B表的每一行都强行“拉郎配”一次。

    • 如果A表有M行,B表有N行,结果集就会有 M * N 行!数据量一大,分分钟把你的数据库搞“爆炸”!🤯
    • 口头禅:“管他三七二十一,全都给我配一遍!宁可错杀一千,不可放过一个(潜在组合)!”
    • 写法SELECT ... FROM tableA CROSS JOIN tableB; 或者 SELECT ... FROM tableA, tableB; (不加任何WHERE连接条件时)
    • 用途:正常业务中用得极少,除非你真的需要所有可能的组合(比如生成测试数据、某些特定的数学运算)。大多数情况下,如果你不小心写出了笛卡尔积,那很可能是你忘了加ONWHERE连接条件了!

了解了这些“司仪”的性格,我们才能更好地指挥它们干活。

第二章:MySQL的“红娘秘籍”——JOIN执行原理大揭秘 🕵️‍♀️📖

当MySQL收到一个JOIN请求后,它内部是怎么运作的呢?难道真的是挨个比较吗?不完全是,它也有一套自己的“相亲算法”。

在MySQL的早期版本以及很多情况下,JOIN操作的核心算法是嵌套循环连接 (Nested Loop Join, NLJ) 及其变种。

2.1 简单嵌套循环连接 (Simple Nested Loop Join, SNLJ) - “老实人的笨办法” 🐢

这是最原始、最容易理解,但也通常是最低效的一种。

  • 算法描述

    1. 选择一个表作为“外层表”(也叫驱动表,Driving Table)。
    2. 遍历外层表的每一行。
    3. 对于外层表的每一行,都去遍历“内层表”(也叫被驱动表,Driven Table)的所有行,找到匹配的行,然后组合输出。
  • 伪代码示意

    FOR each row R1 in OuterTable:FOR each row R2 in InnerTable:IF R1 joins with R2 ON join_condition:Output (R1, R2)
    
  • 性能噩梦:如果外层表有M行,内层表有N行,那么总的比较次数大约是 M * N!如果内外层表都没有索引,那每次在内层表查找都是全表扫描,I/O次数大约是 M + M*N(外层扫一遍,内层扫M遍)。数据量一大,简直是“龟速行驶”。

  • MySQL的“嫌弃”:由于SNLJ效率太低,现代MySQL优化器会极力避免使用它,除非万不得已(比如连接条件极其复杂,没有任何索引可用)。

2.2 索引嵌套循环连接 (Index Nested Loop Join, INLJ) - “聪明人的快捷方式” 🚀

当被驱动表(内层表)的连接字段上有索引时,情况就大不一样了!INLJ闪亮登场!

  • 算法描述

    1. 选择一个表作为“外层表”(驱动表)。
    2. 遍历外层表的每一行。
    3. 对于外层表的每一行,不再全表扫描内层表,而是拿着外层表的连接字段值,通过内层表连接字段上的索引,直接“精准定位”到内层表中匹配的行。
  • 伪代码示意

    FOR each row R1 in OuterTable:LOOKUP R2 in InnerTable USING INDEX ON InnerTable.join_column WHERE InnerTable.join_column = R1.join_column_value:IF R2 is found:Output (R1, R2)
    
  • 性能飞跃

    • I/O次数:如果外层表M行,内层表通过索引查找(假设是B+树索引,理想情况是logN或常数级别),总的I/O次数大约是 M + M * (索引查找成本)。相比SNLJ的M + M*N,效率提升是数量级的!
    • EXPLAIN中的信号:当你用EXPLAIN分析JOIN语句时,如果看到被驱动表的typeeq_ref (对于唯一索引/主键连接) 或 ref (对于普通二级索引连接),通常就意味着用上了INLJ,这是个好兆头!
    -- 假设 students.class_id 和 classes.id 都有索引,且 classes.id 是主键
    EXPLAIN SELECT s.name, c.class_name
    FROM students s
    INNER JOIN classes c ON s.class_id = c.id;-- 可能的EXPLAIN结果:
    
    idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
    1SIMPLEsALLidx_class_idNULLNULLNULL1000
    1SIMPLEceq_refPRIMARYPRIMARY4test.s.class_id1

    – (这里students是驱动表,classes是被驱动表,classes.id用了主键索引,type=eq_ref,完美!)

2.3 块嵌套循环连接 (Block Nested Loop Join, BNL) - “批量相亲,减少跑腿” 🚌

当被驱动表(内层表)的连接字段上没有可用索引时,SNLJ太慢,MySQL又不想坐以待毙,于是就有了BNL。这通常是MySQL在无法使用INLJ时的“无奈之举”。

  • 算法描述

    1. 选择一个表作为“外层表”(驱动表)。
    2. 开辟一块内存区域,叫做Join Buffer(大小由join_buffer_size参数控制)。
    3. 从外层表一次性读取一批行(比如10行、100行,取决于Join Buffer能装多少),把这些行的连接字段值和需要查询的列都放到Join Buffer里。
    4. 然后,全表扫描一次内层表
    5. 对于内层表的每一行,都跟Join Buffer中缓存的所有外层表行进行匹配。
    6. 重复步骤3-5,直到外层表的所有行都处理完毕。
  • 伪代码示意

    Initialize JoinBuffer
    FOR each row R1 in OuterTable:IF JoinBuffer is full:FOR each row R2 in InnerTable: // Scan InnerTable onceFOR each buffered_R1 in JoinBuffer:IF buffered_R1 joins with R2 ON join_condition:Output (buffered_R1, R2)Clear JoinBufferStore R1's relevant columns in JoinBuffer// Process any remaining rows in JoinBuffer (last batch)
    IF JoinBuffer is not empty:FOR each row R2 in InnerTable: // Scan InnerTable again (potentially)FOR each buffered_R1 in JoinBuffer:IF buffered_R1 joins with R2 ON join_condition:Output (buffered_R1, R2)
    
  • 性能特点

    • 减少了内层表的扫描次数:相比SNLJ(内层表扫M遍),BNL中内层表被扫描的次数是 外层表总行数 / Join Buffer能容纳的外层表行数。如果Join Buffer足够大,甚至可能只扫描内层表一次(理想情况,很少见)。
    • 依然是磁盘I/O大户:因为内层表还是全表扫描。
    • EXPLAIN中的信号Extra列出现 Using join buffer (Block Nested Loop)
  • join_buffer_size的关键:这个参数的大小直接影响BNL的效率。Buffer越大,一次能缓存的外层表行越多,内层表的扫描次数就越少。但要注意,这个Buffer是每个连接独享的,设置过大可能导致内存问题(详见上一篇《全局参数优化》)。

2.4 MySQL 8.0+ 的新贵:哈希连接 (Hash Join) 🧙‍♂️

从MySQL 8.0.18版本开始,当JOIN操作无法使用索引(即BNL的适用场景)时,MySQL引入了更高效的哈希连接 (Hash Join) 算法,并在后续版本中逐渐用它来替代BNL。

  • 算法描述 (简化版)

    1. 选择一个表作为“构建表”(通常是较小的那个表)。
    2. 读取构建表的所有行,根据连接字段计算哈希值,并在内存中构建一个哈希表 (Hash Table)。哈希表的Key是哈希值,Value是指向原始行的指针或行数据本身。
    3. 然后,选择另一个表作为“探测表”(通常是较大的那个表)。
    4. 逐行读取探测表,对于每一行,同样根据连接字段计算哈希值。
    5. 用这个哈希值去哈希表中“探测”是否存在匹配的行。如果哈希值匹配,再进一步比较原始连接字段的值是否精确相等。
    6. 如果匹配成功,则组合输出。
  • 性能优势

    • 通常比BNL效率更高,尤其是在连接的表都比较大,且Join Buffer无法完全容纳构建表时。
    • 构建哈希表和探测哈希表的操作,平均时间复杂度接近O(1)。
  • EXPLAIN中的信号:在MySQL 8.0.18+,如果JOIN无法使用索引,你可能会在Extra列看到类似 Using hash join 或在EXPLAIN FORMAT=JSON的输出中看到hash_join的执行计划。

  • 内存依赖:哈希连接也需要内存来构建哈希表。如果内存不足以容纳整个构建表的哈希表,MySQL可能会采用更复杂的“分块哈希连接”或“溢出到磁盘的哈希连接”,性能会有所下降,但通常仍优于BNL。

  • 替代BNL:在MySQL 8.0.20及更高版本中,BNL被哈希连接完全取代。也就是说,即使你看到Using join buffer,其底层实现也可能是哈希连接了。

小结JOIN执行算法
MySQL优化器会“绞尽脑汁”地选择最高效的JOIN算法。它的首选永远是INLJ(用上索引),因为这通常是最快的。如果实在没办法用索引,它在8.0之前会退而求其次用BNL,在8.0.18之后则更倾向于用更强大的Hash Join。

第三章:JOIN优化的“葵花宝典”——核心法则与实战技巧 🚀📖

知道了MySQL是如何“相亲”的,我们就能对症下药,写出让它“一见钟情”的JOIN语句了!

1. 索引!索引!还是TMD索引!(最重要的事说三遍) 🔑🔑🔑

这是JOIN优化的第一金科玉律,没有之一!

  • 给谁加索引?
    • 连接条件中的列ON tableA.col1 = tableB.col2,那么tableA.col1tableB.col2都应该是索引候选者。尤其是被驱动表(内层表)的连接列,必须要有索引!
    • WHERE子句中用于筛选的列。
    • ORDER BYGROUP BY 中用到的列。
  • 索引类型:B-Tree索引是JOIN的好朋友。
  • 数据类型要一致:确保连接字段的数据类型、字符集、排序规则都完全一致。如果类型不匹配(比如一个INT,一个VARCHAR),MySQL可能需要进行隐式类型转换,这会导致索引失效!
    • 坏例子ON users.user_id (INT) = orders.user_id_str (VARCHAR) -> 索引可能失效!
    • 好例子ON users.user_id (INT) = orders.user_id (INT)
  • 避免在连接字段上使用函数:和普通查询一样,ON FUNC(tableA.col) = tableB.col也会让tableA.col上的索引失效(除非你用了MySQL 8.0的函数索引)。

段子手吐槽
不给JOIN列加索引,就像派了一个近视800度的士兵去战场上肉眼索敌,然后你还怪他打不准?!给他配个“八倍镜”(索引)啊,大哥!

2. 驱动表的选择:“谁先动筷子”的艺术 🥢

在嵌套循环类的JOIN中(SNLJ, INLJ, BNL),驱动表(外层表)的选择对性能有很大影响。

  • MySQL优化器的选择
    • 通常,MySQL优化器会尝试选择结果集行数较少的那个表作为驱动表(在应用了WHERE条件过滤之后)。因为驱动表会被完整扫描(或部分扫描),它的行数越少,外层循环的次数就越少。
    • 对于INNER JOIN,优化器有权调整表的连接顺序。
    • 对于LEFT JOIN,左表固定为驱动表。
    • 对于RIGHT JOIN,右表固定为驱动表(或者MySQL可能将其改写为等价的LEFT JOIN再处理)。
  • 人工干预:STRAIGHT_JOIN
    如果你觉得MySQL优化器选的驱动表“不够明智”(比如统计信息不准导致误判),你可以用STRAIGHT_JOIN关键字来“强制”指定连接顺序。SELECT ... FROM tableA STRAIGHT_JOIN tableB ...会强制tableA作为驱动表。
    • 何时使用? 仅当你有充分的理由和测试数据证明优化器选错了,并且STRAIGHT_JOIN能带来明显性能提升时才考虑。大多数情况下,相信优化器。
    • 风险:如果你的判断是错的,STRAIGHT_JOIN反而可能让性能更差。

驱动表选择原则(通用思路)

  • 小表驱动大表:尽量让行数较少的表(经过WHERE过滤后)作为驱动表。
  • 被驱动表连接列有索引是前提:无论谁驱动谁,保证被驱动表的连接列上有高效索引是必须的。

3. 过滤条件要“给力”:尽可能早地减少结果集 📉

  • WHERE子句 VS ON子句

    • 对于INNER JOINWHEREON中的条件在逻辑上是等价的,MySQL优化器可能会重新安排它们的执行顺序。但通常建议连接相关的条件写在ON中,单表筛选条件写在WHERE,更清晰。
    • 对于LEFT JOIN / RIGHT JOIN (OUTER JOIN):ONWHERE的条件位置非常重要!
      • ON条件:是在生成临时连接结果集之前就用来筛选被驱动表(对于LEFT JOIN是右表,对于RIGHT JOIN是左表)的记录的。如果被驱动表的记录不满足ON条件,它就不会参与连接,其对应的驱动表行在结果中相关列为NULL。
      • WHERE条件:是在临时连接结果集(驱动表所有行 + 匹配上的被驱动表行或NULL)生成之后,再对这个结果集进行最终的筛选。
      • 关键区别:如果把针对被驱动表的筛选条件错放到WHERE子句中,对于OUTER JOIN,可能会导致本应保留的驱动表行(因为OUTER JOIN的特性)因为被驱动表部分为NULL而不满足WHERE条件,从而被错误地过滤掉,使得OUTER JOIN的行为退化成类似INNER JOIN。
      • 最佳实践:对于OUTER JOIN,如果想根据被驱动表的列来限制哪些行可以参与连接,务必把这些条件写在ON子句里!
        -- 需求:查询所有学生及其数学课的成绩(没有数学成绩的也显示学生,成绩为NULL)
        -- 正确写法 (筛选数学课的条件在ON里)
        SELECT s.name, sc.score
        FROM students s
        LEFT JOIN scores sc ON s.student_id = sc.student_id AND sc.subject = '数学';-- 错误写法 (筛选数学课的条件在WHERE里,会导致没有数学成绩的学生整行被过滤掉)
        SELECT s.name, sc.score
        FROM students s
        LEFT JOIN scores sc ON s.student_id = sc.student_id
        WHERE sc.subject = '数学'; -- 这实际上变成了INNER JOIN的效果
        
  • 尽早过滤:通过在WHERE子句或ON子句中添加有效的筛选条件,尽早地把不需要的数据行给“咔嚓”掉,这样参与JOIN运算的行数就少了,性能自然提升。

4. join_buffer_size:不是万能丹,合理使用才有效 💊

  • 何时起作用:只有当JOIN操作无法使用索引,MySQL被迫使用BNL或Hash Join时,join_buffer_size才派得上用场。
  • 不是越大越好:它是个“连接独享”的内存区域。如果设太大,并发连接一多,内存就爆了。
  • 优先解决索引问题:调大join_buffer_size是“治标不治本”的。首要任务永远是检查并优化JOIN列的索引!
  • 文档建议:除非你确定有很多无法避免的、需要大量内存的无索引JOIN,否则不建议将此值设得过大。几MB到十几MB通常是上限。

5. EXPLAIN:你的JOIN优化“导航仪” 🗺️

不厌其烦地再次强调EXPLAIN的重要性!对于任何你觉得慢的JOIN查询,第一件事就是把它扔给EXPLAIN“体检”一下。

  • 重点关注的列
    • table: 表的读取顺序(大致反映了驱动表和被驱动表的顺序)。
    • type: 连接类型!这是判断JOIN效率的核心。
      • 最佳system > const > eq_ref (唯一索引/主键JOIN) > ref (普通二级索引JOIN)
      • 较差ref_or_null > index_merge > unique_subquery > index_subquery
      • 糟糕range > index (全索引扫描) > ALL (全表扫描)
      • 对于JOIN,如果被驱动表的typeeq_refref,说明索引用上了,很好!如果是ALLindex,那就要警惕了,可能是BNL或Hash Join。
    • possible_keys: 可能用到的索引。
    • key: 实际用到的索引。如果是NULL,说明没用上索引。
    • key_len: 用到的索引长度。越短越好(在能区分记录的前提下)。
    • ref: 显示了哪些列或常量被用于索引查找。
    • rows: MySQL估计需要扫描的行数。越小越好。
    • Extra: 包含大量重要信息!
      • Using index: 覆盖索引,非常好!
      • Using where: 使用了WHERE子句进行过滤。
      • Using temporary: 可能用了临时表(比如GROUP BYUNION操作)。
      • Using filesort: 文件排序,性能杀手,需要优化ORDER BY或相关索引。
      • Using join buffer (Block Nested Loop): 说明用了BNL算法。
      • Using join buffer (Batched Key Access): BKA是一种优化的BNL,结合了MRR(Multi-Range Read)。
      • Using hash join (MySQL 8.0.18+): 说明用了哈希连接。
      • Not exists: 用于反连接优化。

通过仔细解读EXPLAIN的输出,你就能诊断出JOIN的瓶颈在哪里,是索引没用上?还是驱动表选错了?还是Join Buffer太小(或者说,应该加索引)?

6. MySQL 8.0+ 的其他JOIN优化特性 (锦上添花) 🌸

除了Hash Join,MySQL 8.0还在JOIN优化方面做了一些其他改进:

  • Lateral Derived Tables (LATERAL关键字):MySQL 8.0.14引入。允许派生表(子查询)引用FROM子句中在它之前定义的表的列。这可以实现一些以前很难或效率低下的“依赖性JOIN”。
  • 优化器对IN子查询的转换:很多IN (SELECT ...)的子查询会被优化器转换为更高效的JOIN。
  • 更智能的代价模型:优化器在选择执行计划时,会基于更精确的成本估算。

7. JOIN查询的“七宗罪” (常见避坑指南) 🚫

  1. “无情”笛卡尔积:忘了写ONWHERE连接条件,或者条件写错导致全匹配。
  2. “裸奔”连接列:连接字段没有索引,或者索引失效(类型不匹配、用函数等)。
  3. “贪婪”SELECT:明明只需要几列,却非要SELECT *,尤其是在JOIN大表时,会增加大量不必要的IO和网络传输,也可能让覆盖索引失效。按需索取,才是王道!
  4. “迷糊”OUTER JOIN条件:把本该放在ON里的被驱动表筛选条件,错放到了WHERE里,导致结果不符合预期。
  5. “臃肿”大事务JOIN:在一个超大的事务里执行复杂的JOIN,长时间锁住资源,影响并发。
  6. “盲目”相信优化器/“过度”人工干预:既不能完全不看EXPLAIN就上线,也不能芝麻大点事就用STRAIGHT_JOINFORCE INDEX。先理解,再优化。
  7. “忽视”数据分布和统计信息:如果表的统计信息严重过时或不准确,优化器可能会做出错误的执行计划。定期ANALYZE TABLE可能有助于更新统计信息。

第四章:实战演练——看个例子压压惊 👨‍🏫

假设我们有两张表:

  • employees (员工表): emp_no (PK), first_name, last_name, hire_date, dept_no (FK, 有索引)
  • departments (部门表): dept_no (PK), dept_name

查询需求:找出所有在 ‘Sales’ 部门,并且是在 '2023-01-01’之后入职的员工姓名。

糟糕的写法 (可能)

-- 假设departments表非常大,employees表相对较小
SELECT e.first_name, e.last_name
FROM departments d, employees e -- 隐式JOIN,容易写漏条件
WHERE d.dept_name = 'Sales'AND e.hire_date > '2023-01-01'AND e.dept_no = d.dept_no; -- 连接条件放最后,可读性稍差

优化思路与较好的写法

  1. 明确JOIN类型和连接条件:使用显式INNER JOIN
  2. 索引检查:确保employees.dept_no, departments.dept_no, employees.hire_date都有索引。departments.dept_name也最好有索引,如果经常用它查询。
  3. 驱动表考量
    • 如果departments.dept_name = 'Sales'能筛选出很少的部门(比如就1个),那么departments作为驱动表可能更好。
    • 如果employees.hire_date > '2023-01-01'能筛选出很少的员工,那么employees作为驱动表可能更好。
    • MySQL优化器通常会尝试估算。

推荐写法

SELECT e.first_name, e.last_name
FROM employees e
INNER JOIN departments d ON e.dept_no = d.dept_no -- 连接条件清晰
WHERE d.dept_name = 'Sales'                      -- 筛选条件1AND e.hire_date > '2023-01-01';                -- 筛选条件2-- 使用EXPLAIN分析:
EXPLAIN SELECT e.first_name, e.last_name
FROM employees e
INNER JOIN departments d ON e.dept_no = d.dept_no
WHERE d.dept_name = 'Sales'AND e.hire_date > '2023-01-01';

EXPLAIN结果分析要点

  • 看哪个表是驱动表,哪个是被驱动表。
  • 看被驱动表的type是不是eq_refref
  • key列是否都用上了合适的索引。
  • rows列估算的扫描行数是不是尽可能小。
  • Extra列有没有Using filesort或不希望出现的Using join buffer

如果发现性能不佳,比如EXPLAIN显示某个表的typeALL,那就要重点检查该表的连接列和WHERE条件列的索引情况。

总结:JOIN优化🧘‍♂️

呼!关于MySQL的JOIN优化,咱们今天这趟“星际穿越”算是把主要景点都逛了一遍。从JOIN的种类、执行原理,到各种优化秘籍和避坑指南,信息量确实不小。

但记住,JOIN优化不是一门“玄学”,它是有章可循的科学。核心就三点:

  1. 让索引飞起来! (尤其是被驱动表的连接列)
  2. 让数据量小下去! (通过有效的WHEREON条件尽早过滤)
  3. 让算法跑起来! (理解MySQL如何选择JOIN算法,并创造条件让它选最优的)

而这一切的基础,都离不开你对EXPLAIN输出的“火眼金睛”般的解读能力。

JOIN优化,就像当一个数据库界的“月老”,你的目标就是用最少的“相亲成本”(系统资源),让合适的“男女嘉宾”(数据行)最高效地“牵手成功”(组合成结果)。这需要你对双方(表结构、数据分布、索引情况)都有深入的了解,还需要一点点“成人之美”的耐心和智慧。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/905770.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TLS 1.3黑魔法:从协议破解到极致性能调优

一、TLS协议逆向工程实验 1.1 密码学套件破解剧场 实验准备: 靶机:启用TLS 1.2的Nginx服务器 工具集:Wireshark OpenSSL s_client 定制Python脚本 实战攻击复现: # 强制使用弱加密套件连接 openssl s_client -connect exa…

国标GB/T 12536-90滑行试验全解析:纯电动轻卡行驶阻力模型参数精准标定

摘要 本文以国标GB/T 12536-90为核心框架,深度解析纯电动轻卡滑行试验的完整流程与数据建模方法,提供: 法规级试验规范:从环境要求到数据采集全流程详解行驶阻力模型精准标定:最小二乘法求解 ( FAv^2BvC ) 的MATLAB实…

【GaussDB迁移攻略】DRS支持CDC,解决大规模数据迁移挑战

目录 1 背景介绍 2 CDC的实现原理 3 DRS的CDC实现方式 4 DRS的CDC使用介绍 5 总结 1 背景介绍 随着国内各大行业数字化转型的加速,客户的数据同步需求越来越复杂。特别是当需要将一个源数据库的数据同时迁移到不同的目标库场景时,华为云通常会创建…

PSA Certified

Arm 推出的 PSA Certified 已成为安全芯片设计领域的黄金标准。通过对安全启动、加密服务以及更新协议等方面制定全面的要求,PSA Certified为芯片制造商提供了清晰的路线图,使其能将安全机制深植于定制芯片解决方案的基础架构中。作为对PSA Certified的补…

游戏引擎学习第286天:开始解耦实体行为

回顾并为今天的内容定下基调 我们目前正在进入实体系统的一个新阶段,之前我们已经让实体的移动系统变得更加灵活,现在我们想把这个思路继续延伸到实体系统的更深层次。今天的重点,是重新审视我们处理实体类型(entity type&#x…

遥感图像非法采矿矿区识别分割数据集labelme格式1818张3类别

数据集格式:labelme格式(不包含mask文件,仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数):1818 标注数量(json文件个数):1818 标注类别数:3 标注类别名称:["river","illegal-mining"…

python爬虫实战训练

前言:哇,今天终于能访问豆瓣了,前几天爬太多次了,网页都不让我访问了(要登录)。 先来个小练习试试手吧! 爬取豆瓣第一页(多页同上篇文章)所有电影的排名、电影名称、星…

Go语言实现生产者-消费者问题的多种方法

Go语言实现生产者-消费者问题的多种方法 生产者-消费者问题是并发编程中的经典问题,涉及多个生产者生成数据,多个消费者消费数据,二者通过缓冲区(队列)进行协调,保证数据的正确传递和同步。本文将从简单到…

【Opencv】canny边缘检测提取中心坐标

采用opencv 对图像中的小球通过canny边缘检测的方式进行提取坐标 本文介绍了如何使用OpenCV对图像中的小球进行Canny边缘检测,并通过Zernike矩进行亚像素边缘检测,最终拟合椭圆以获取小球的精确坐标。首先,图像被转换为灰度图并进行高斯平滑…

蓝桥杯12届国B 123

题目描述 小蓝发现了一个有趣的数列,这个数列的前几项如下: 1,1,2,1,2,3,1,2,3,4,⋯ 小蓝发现,这个数列前 1 项是整数 1,接下来 2 项是整数 1 至 2,接下来 3 项是整数 1 至 3,接下来 4 项是整数 1 至 4&…

鸿蒙OSUniApp 制作动态加载的瀑布流布局#三方框架 #Uniapp

使用 UniApp 制作动态加载的瀑布流布局 前言 最近在开发一个小程序项目时,遇到了需要实现瀑布流布局的需求。众所周知,瀑布流布局在展示不规则尺寸内容(如图片、商品卡片等)时非常美观和实用。但在实际开发过程中,我…

ThinkStation图形工作站进入BIOS方法

首先视频线需要接在独立显卡上,重新开机,持续按F1,或者显示器出来lenovo的logo的时候按F1,这样就进到bios里了。联*想*坑,戴尔贵。靠。

【源码级开发】Qwen3接入MCP,企业级智能体开发实战!

Qwen3接入MCP智能体开发实战(上) 一、MCP技术与Qwen3原生MCP能力介绍 1.智能体开发核心技术—MCP 1.1 Function calling技术回顾 如何快速开发一款智能体应用,最关键的技术难点就在于如何让大模型高效稳定的接入一些外部工具。而在MCP技术…

Linux下载与安装

一、YUM 1.1 什么是YUM 在CentOS系统中,软件管理方式通常有三种方式:rpm安装、yum安装以及编译(源码)安装。 编译安装,从过程上来讲比较麻烦,包需要用户自行下载,下载的是源码包,需…

PostgreSQL中的全页写

一、概述 在PGSQL数据库中,默认的页面大小为8KB,但是磁盘buffer的大小为4KB,扇区大小为512B。这就导致在操作系统的角度看数据库的写操作,其实并不是一种原子操作。如果操作系统发生了系统级别的故障,此时正好操作系统…

WEB安全--Java安全--shiro550反序列化漏洞

一、前言 什么是shiro? shiro是一个Apache的Java安全框架 它的作用是什么? Apache Shiro 是一个强大且灵活的 Java 安全框架,用于处理身份验证、授权、密码管理以及会话管理等功能 二、shiro550反序列化原理 1、用户首次登录并勾选记住密码…

2024 睿抗机器人开发者大赛CAIP-编程技能赛-专科组(国赛)解题报告 | 珂学家

前言 题解 2024 睿抗机器人开发者大赛CAIP-编程技能赛-专科组(国赛),陈越姐姐出题。 国赛比省赛,难度增强了不少,题目就剩下4个题了。 涉及堆栈,hash表,优先队列等高阶数据结构的使用&#x…

15 C 语言字符类型详解:转义字符、格式化输出、字符类型本质、ASCII 码编程实战、最值宏汇总

1 字符类型概述 在 C 语言中,字符类型 char 用于表示单个字符,例如一个数字、一个字母或一个符号。 char 类型的字面量是用单引号括起来的单个字符,例如 A、5 或 #。 当需要表示多个字符组成的序列时,就涉及到了字符串。在 C 语言…

操作系统-锁/内存/中断/IO

文章目录 锁自旋锁互斥锁悲观锁和乐观锁 内存管理物理/虚拟内存页表段表虚拟内存布局写时复制copy on writebrk,mmap页面置换算法 中断中断分类中断流程 网络I/OI/O模型服务器处理并发请求 锁 自旋锁 自旋锁是一种基于忙等待(Busy-Waiting)…

割点与其例题

割点 定义: 若一个点在图中被去掉后,图的连通块个数增加,那么这个点就被称为“割点”。如下图所示红点。 定义说白了就是若去掉一个点,图被“断开”的点称为割点。 朴素算法: 枚举每个点 u。遍历图,如果…