日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

left join 索引失效无条件_技术分享 | MySQL 优化:JOIN 优化实践

發布時間:2024/1/23 数据库 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 left join 索引失效无条件_技术分享 | MySQL 优化:JOIN 优化实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
近期剛好學習了丁奇老師的《MySQL 實戰 45 講》中的 join 優化相關知識,又剛剛好碰上了一個非常切合的 join 查詢需要優化,分析過程有些曲折,記錄下來留作筆記。問題 SQL 描述問題 SQL 和執行計劃是這樣的:

explain SELECT

t1.stru_id AS struId,

...

FROM cams_stru_info t1

LEFT JOIN cams_mainframerel t2 ON t1.stru_id =t2.stru_id

WHERE t1.stru_state="1";

這個 SQL 是非常簡單的,關聯條件 stru_id 在兩張表中都是主鍵或者主鍵的第一個字段:

而把 left join 轉化成 inner join 后,SQL的效率很高:

從上述信息來看,這個 SQL 存在的問題有:

1. 大表驅動小表,這肯定是不好的,t1表近11萬行數據,為驅動表;t2表近1.9萬行數據,為被驅動表。這主要是 left join 導致的,大部分情況下 left join 左表即驅動表,但是這里業務需求就是如此,沒辦法改變;

2. 驅動表的篩選條件 stru_state = 1,這個字段是一個狀態值,基數很小,不適合建索引,即使建索引也沒有用,所以驅動表一定是全表掃描。這點根據業務需求,也沒法改變,其實全表掃描對性能影響不大,后續會解釋;

3. 被驅動表關聯字段明明有索引,但做了全表掃描(全索引掃描);

4. 優化器選擇使用的 join 算法為 BNL(Block Nested Loop),SQL 執行是計算次數等于 11 萬 * 1.9 萬,近 20 億次計算,所以執行非常慢。

join 的兩種算法:BNL 和 NLJ

在繼續分析之前,先得介紹一下 join 的兩種算法,方便大家理解后面我分析思路上的錯誤和心得。首先是 NLJ(Index Nested-Loop Join)算法,以如下 SQL 為例:

select * from t1 join t2 on t1.a=t2.a

SQL 執行時內部流程是這樣的:

1. 先從 t1(假設這里 t1 被選為驅動表)中取出一行數據 X;

2. 從 X 中取出關聯字段 a 值,去 t2 中進行查找,滿足條件的行取出;

3.?重復1、2步驟,直到表 t1 最后一行循環結束。

這就是一個嵌套循環的過程,注意“Index”,所以這里前提是被驅動表的關聯字段有索引,最明顯的特征就是在被驅動表上查找數據時可以使用索引,總的對比計算次數等于驅動表滿足 where 條件的行數。假設這里 t1、t2都是1萬行,則只需要 1萬次計算。

如果 t1、t2 的 a 字段都沒有索引,還按照上述的嵌套循環流程查找數據呢?每次在被驅動表上查找數據時都是一次全表掃描,要做1萬次全表掃描,掃描行數等于 1萬+1萬*1萬,這個效率很低,如果表行數更多,掃描行數動輒幾百億,所以優化器肯定不會使用這樣的算法,而是選擇 BNL 算法,執行流程是這樣的:

1. 把 t1 表(假設這里 t1 被選為驅動表)滿足條件的數據全部取出放到線程的 join buffer 中;

2.?每次取 t2 表一行數據,去 joinbuffer 中進行查找,滿足條件的行取出,直到表 t2 最后一行循環結束。

這個算法下,執行計劃的 Extra 中會出現 Using join buffer(Block Nested Loop),t1、t2 都做了一次全表掃描,總的掃描行數等于 1萬+1萬。但是由于 joinbuffer 維護的是一個無序數組,每次在 joinbuffer 中查找都要遍歷所有行,總的內存計算次數等于1萬*1萬。說句題外話,如果 joinbuffer 維護的是一個哈希表的話,每次查找做一次判斷就能找到數據,效率提升飛快,其實這就是 hash join 了,MySQL 8.0 已支持。另外如果 joinbuffer 不夠大放不下驅動表的數據,則要分多次執行上面的流程,會導致被驅動表也做多次全表掃描。

分析誤區

回到分析過程,我一開始疑惑的點就在于:為什么被驅動表 t2 關聯字段有索引,卻沒有使用 NLJ 算法,而是使用了 BNL 算法?顯然如果使用 NLJ 算法,總的掃描行數等于 t1 的行數即 19萬行,總的計算次數也只有19萬次,效率是很高的。

因為是剛學到 join 算法這方面的知識,理解的不是很透徹,思路上一直糾結在算法這里,所以接下來我想的是禁用 BNL 算法,搜索了一下 hint 語法:"select /+ NO_BNL() */ t1. from ...",執行計劃的結果卻跟我預期的不一樣:

這讓我更迷惑了,明明沒有使用 BNL 算法,為什么被驅動表還是做了全表掃描?是算法出了什么問題嗎?還是 hint 產生了其他效果?

直到客戶告訴了我答案,兩表的關聯字段字符集和校對規則不一樣...

得解釋下為什么之前沒有想這一點,因為前面提到 inner join 執行計劃毫無問題,使用了 NLJ 算法,優化器選了小表 t2 做驅動表,被驅動表 t1 按索引查找,效率很高。

繼續分析

得知原因后,關于算法的疑問突然就想通了,NLJ 和 BNL 算法的選擇根本在于關聯字段的索引:不是取決于有沒有索引,而是被驅動表能不能使用到索引進行查找。所以這本質上是一個索引失效問題,邏輯上其實只推進了一步,但是因為對新知識的不自信,推理能力不足(之前自認為推理能力不錯的...),這一步一直沒有走出去,這應該是我最大的收獲了。

然后還要解釋另一個疑問:既然關聯字段字符集和校對規則不一樣,為什么 inner join 不受影響?left join 時卻索引失效了?

來看個測試,下面是兩張表,關聯字段的字符集不一樣:

CREATE TABLE `t3` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`a` char(50) CHARACTER SET utf8 DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `idx_a` (`a`)) ;

CREATE TABLE `t4` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`b` char(50) CHARACTER SET latin1 DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `idx_b` (`b`));

分別插入了幾條數據,使用 straight_join 語法固定連接順序:

SQL1:select * from t3 straight_join t4 on t3.a=t4.b;

SQL2:select * from t4 straight_join t3 on t3.a=t4.b;

SQL3:select * from t3 left join t4 on t3.a=t4.b;

SQL4:select * from t3 join t4 on t3.a=t4.b;

SQL1 和 SQL3 都是選擇了 t3 做驅動表,執行計劃一樣,都顯示索引失效了,使用了 BNL 算法,被驅動表進行全表掃描:

SQL2 和 SQL4 都是選擇了 t4 做驅動表,執行計劃一樣,被驅動表按照索引查找,使用了 NLJ 算法:

也就是說,在這個測試中,latin1 去 join utf8 時,索引是正常使用的,反過來則索引失效。又測試了 utf8 和 utf8mb4 的情況,utf8 join utf8mb4 正常,反過來則索引失效。為此我的猜測是:被驅動表字段的字符集更大時,索引可以正常使用,反之則索引失效。關于字符集這點就不繼續探索了,希望能有這方面的高手來解答。最后,SQL 改成 inner join 后使用 NLJ 算法的原因就很明了了:NLJ 算法的效率顯然是高于 BNL 的,優化器做選擇時當然要選擇更高效的算法。雖然關聯字段字符集不一樣,但是按照小>大的順序,索引還是可以正常使用,一旦索引可以使用,選擇 NLJ 算法就是順理成章的事了。總結1. NLJ 和 BNL 算法的選擇根本在于關聯字段的索引:不是取決于有沒有索引,而是被驅動表能不能使用到索引進行查找;2. join 查詢關聯字段字符集或者校對規則不一致導致的索引失效,跟關聯順序有關,當然規范一定是讓各表關聯字段的字符集和校對規則一致;3. join 的優化,最好的辦法就是把 BNL 轉化為 NLJ,也就是被驅動表關聯字段加索引,并且保證其有效,更多的優化思路可以看參考資料。另外,一個好消息是從 MySQL8.0.18 開始已經支持 hash join 了,原本選擇 BNL 算法的場景會直接使用 hash join,效率提升不止一點點,簡直就是 DBA 福音了。參考資料:https://time.geekbang.org/column/article/79700https://time.geekbang.org/column/article/80147https://time.geekbang.org/column/article/82865

社區近期動態

?點一下“閱讀原文”了解更多資訊

總結

以上是生活随笔為你收集整理的left join 索引失效无条件_技术分享 | MySQL 优化:JOIN 优化实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。