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.pswp.cn/news/905770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905770.shtml
英文地址,請注明出處:http://en.pswp.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。遍歷圖,如果…