MYSQL8.0 hash join中的笛卡爾積關聯的現象解析

2021-01-14 民生運維人

  在我們的測試過程中,發現hash join 在特殊情況下, 存在笛卡爾積關聯的現象,於是我們對這個現象進行更進一步地探究,以便找出其中的原因。 為了便於模擬以及觀察現象。我們準備3個非常簡單的表以及相應的數據,來進行本次實驗,表跟數據如下:

create table  t1 ( t1_id varchar(20),t1_name varchar(20) ,t1_addr varchar(20));

create table  t2 ( t2_id varchar(20),t2_name varchar(20) ,t2_addr varchar(20));

create table  t3 ( t3_id varchar(20),t3_name varchar(20) ,t3_addr varchar(20));


T1表:T1表總數1000行。

insert into t1 values('a0','name0','addr0'); 

insert into t1 values('a1','name1','addr1'); 

insert into t1 values('a2','name2','addr2'); 

insert into t1 values('a3','name3','addr3'); 

insert into t1 values('a4','name4','addr4'); 

.....................................................................

....................................................................


insert into t1 values('a997','name997','addr997'); 

insert into t1 values('a998','name998','addr998'); 

insert into t1 values('a999','name999','addr999'); 


T2表: T2表總數50000行。

insert into t2 values('a0','name0','addr0'); 

insert into t2 values('a1','name1','addr1'); 

insert into t2 values('a2','name2','addr2'); 

insert into t2 values('a3','name3','addr3'); 

........................................................................

................................................................................

insert into t2 values('a49998','name49998','addr49998'); 

insert into t2 values('a49999','name49999','addr49999'); 


T3表:T3表的總數是5100行。

insert into t3 values('a0','name0','addr0'); 

insert into t3 values('a1','name1','addr1'); 

insert into t3 values('a2','name2','addr2'); 

..................................................................

..........................................................

insert into t3 values('a5097','name5097','addr5097'); 

insert into t3 values('a5098','name5098','addr5098'); 

insert into t3 values('a5099','name5099','addr5099'); 


為了勾起繼續讀下去興趣,這裡拋出一個疑問:在實驗準備中, T1表造數1000行,T2表造數50000行,T3表為啥造數5100行,而不是5000行?5100跟5000有區別嗎?繼續讀下去, 5000跟5100這兩個數字的差別,含有這篇文章的本質內容,本文要講得就是這個。


然後執行下面這個SQL, 執行時間1.30秒。說明一下: 這個資料庫是debug模式安裝的,且跑在自己的虛擬機上, 所以比較慢。正常情況,這點數據量做正常地hash jion ,要不了1.3秒。 


mysql> select count(*) from t1,t2,t3 where t1_id=t2_id and t2_addr=t3_addr; 

++

| count(*) |

++

|     1000 |

++

1 row in set (1.30 sec)


我們看看執行計劃:


表的關聯順序是T1->T2->T3,這是一個非常正常的關聯順序,也是我們想要的關聯順序。


   現在我們回到前面提的問題:」T3表為什麼造數5100行,而不是5000?」。按照我們習慣性的觀點,表的數量越少,sql執行得越快,現在我們的表T3當前有數據5100行,我們來刪除100行記錄, 讓它變成5000行,我們來觀察情況是否有變? 按照習慣性的觀點推理,SQL執行速度至少不會變慢。 因為表的數據量少了,而且減少得也不多,就100行,執行計劃變化的可能性也不大。


mysql> delete from t3 order by t3_id desc limit 100;

Query OK, 100 rows affected (0.44 sec)


mysql> select count(*) from t3;

++

| count(*) |

++

|     5000 |

++

1 row in set (0.37 sec)


然後執行之前的SQL,觀察是否發生些什麼?

mysql> select count(*) from t1,t2,t3 where t1_id=t2_id and t2_addr=t3_addr; 

++

| count(*) |

++

|      900 |

++

1 row in set (30.87 sec)


這個SQL的執行時間從1.3秒變成了30秒,因為我們刪除了100行的數據,SQL的執行速度反而下降了,那一定是執行計劃變了,變成了什麼樣子呢?


這次表的關聯順序變成了T1->T3->T2,  但T1跟T3之間,在這個示列SQL中,T1跟T3本身沒有直接地關聯關係,所以造成了笛卡爾積關聯,關聯後將產生1000 * 5000 條臨時記錄,這就是SQL執行時間從1.3秒變成30秒的直接原因。


      T3表的數量從5100行變成5000行,導致SQL的執行計劃發生了變化。 對數據進一步變化是否會對執行計劃產生影響,有興趣實驗的話,可以自己有空的時候動手測試一下。例如將T3表的數量從5000繼續往下降,或者從5100繼續往上漲 , 看看是否會導致執行計劃的改變。


      以上是實驗現象, 現在來透過現象去了解背後的原因,作者將目前了解的原理跟大家描述一下(註:特別精簡版,僅描述示列SQL的關鍵執行路徑)。

      1. 首先優化器在編排表的關聯順序之前,先把要關聯的表進行排序,有依賴關係的話需要優先確定表的依賴關係,如外連接中的依賴關係,嵌套關聯中ref訪問等,依賴關係影響排序位置。如果沒有依賴關係,按照表的數量進行排序,因為T1,T2,T3表之間的關聯,不是外連接,也沒有索引,不可能存在ref類型的訪問,所以按照表的數量排定順序,順序是t1->t3->t2. 我們把這個順序命名為tab_order. 具體實現這個邏輯的代碼如下:


      因為該SQL是內連接,且沒有索引,所以前面的條件都不滿足,依靠比較表的行數的多少來排定順序。 命中代碼行如下(下面二行中的其中一行 ):

    if (jt1->found_records > jt2->found_records) return false;

    if (jt1->found_records < jt2->found_records) return true;

    該順序是第一次排序,非最終join順序

下面是這個Join_tab_compare_default函數的註解:


      2. 然後優化器才進行表關聯順序的編排,規則如下:

         因為通過前面的計算,按照表的行數的多少排定的tab_order順序是t1->t3->t2 。所以,首先取出T1表,然後確定T1表的訪問方式,因為沒有索引,只能全表掃描,掃描成本計算方式如下:

         IO成本+取數據的成本(通常也稱CPU成本)。 因為表很小,IO成本比較小,主要是取數成本。在通過執行explain format=tree命令 的輸出中,顯示T1的cost為101.25.  由表的行數1000*0.100000000....1(我們把它當成0.1後,也就是100) 計算得出取數成本100(cpu成本), 再加上IO成本1, 總共101.


        接下來,T1表選擇先計算跟T3表關聯的成本(因為在前面提到的tab_order順序中,順序為T1->T3->T2),因為表T1 跟T3之間沒有關聯關係,所以做笛卡爾積關聯,T3跟T1關聯後的結果是5000*1000,也就是500萬,取數成本為50萬,再加上表T1,表T3的IO成本,總成本就是執行計劃圖中顯示的500105.88。(因為IO成本佔比實在太小,後面在計算成本時,作者可能會直接忽略這部分)。


            然後,再跟T2表進行關聯,關聯成本計算完畢之後,第一次得出這三個表之間關聯的全路徑T1->T3->T2的關聯成本,方法同前,因為表T1跟T3表笛卡爾積關聯,關聯後記錄數為500萬,表T2的總數為50000, 所以取數成本就是500百萬*5萬*0.1係數=250億。我們再來看上面出現過的T1->T3->T2執行計劃圖,完整路徑的總代價為251億(有差別是因為浮點計算,還有係數按照0.1計算,而不是0.1........1)。


       在第一次全路徑計算完成之後,再回退到上一層(執行計劃生成函數用得是遞歸算法,到頂之後回退),於是選擇T2表跟T1表進行關聯,這一步計算完成之後,可能就決定了T1表到底跟T2表關聯,還是跟T3表進行關聯。(為什麼說可能,因為可能會進行全路徑成本的比較,也可能不需要,這就是默認的優化器「偷懶」的操作,規則是局部成本最優。如果T1->T2的關聯成本優於T1->T3,則繼續計算全路徑T1->T2->T3三表關聯的成本,如果T1->T2的關聯成比T1->T3高,直接拋棄,不再擴展這條線路上的路徑)。

      下面我們來計算T1->T2的關聯成本,T1表1000行,T2表50000行,笛卡爾積之後是5000萬,但是T1跟T2之間有等於條件,所以會產生過濾,過濾效果怎麼計算? 因為表T2的T2_id沒有索引,怎麼去估算這個T1_id=T2_id條件的過濾效果? 所以問題就出現在這裡, MySQL對無法估算過濾效果的過濾條件,給一個統一的默認值,這個默認值就是0.1f. 所以MYSQL優化器認為T1跟T2關聯後會產生比500萬條還多一點點的記錄(浮點計算的原因),認為比T1表跟T3表做笛卡爾積關聯後的數量多(T1跟T3表關聯後是500萬,看到這裡,可能會明白這個實驗案例中T3表的數據量是精準設計的),所以優化器選擇了將T1表與T3表進行笛卡爾積的關聯。因此,如果T3的數量再大一些,表T1跟表T3關聯後的笛卡爾積的數量將超過500萬, 那優化器將選擇T1表跟T2表進行關聯。 但實際上, T1表跟T2表關聯後的記錄才1000條, 因為優化器沒有辦法知道關聯後到底會有多少條記錄,所以給了一個默認值0.1係數,按照十分一的比例進行過濾。

         上面只是理論分析了T1表跟T2表關聯的成本,我們來看一下T1跟T2表關聯的實際成本,這次需要通過關鍵字straight_join強制指定關聯順序,不然優化器會選擇T1跟T3表做笛卡爾積關聯。

explain  format=tree select count(*) from t1 straight_join t2 straight_join t3 where t1_id=t2_id and t2_addr=t3_addr;

表的關聯順序為T1->T2->T3, T1跟T2表關聯後的成本為:

cost=5023259.16, rows=5023100



而按照T1->T3->T2關聯順序進行關聯, 則如下(這個圖片在前面出現過,現重新再拿來對比)


表T1->T3關聯後的成本為:

    cost=500105.88, rows=5000000


將這個兩個執行計劃的成本進行比較:


cost比較:500105.88<5023259.16, 行數比較:5000000<5023100 , 所以優化器選擇了T1->T3表進行笛卡爾積關聯。因為這兩個不同路徑(或者方案)的執行成本已經非常接近,所以當T3表再增大一點點,在本次實驗案例中,是將其數量從5000,變成5100之後, 優化器將選擇T1表將跟T2表進行關聯,這樣就出現了表的數量增加了,執行速度反而變快了的「非常規現象」。


 


相關焦點

  • 笛卡爾積
    多表SQL關聯,相信大家都會寫,但是它背後的原理,你可能並不知道,今天我們來拆解一下多表SQL關聯,如果你已經明白什麼是笛卡爾積,那麼可以略過了
  • mysql 版本號解釋_mysql workbench查詢mysql版本號 - CSDN
    SELECT * FROM A,B(,C)或者SELECT * FROM A CROSS JOIN B (CROSS JOIN C)#沒有任何關聯條件,結果是笛卡爾積,結果集會很大,沒有意義,很少使用內連接(INNER JOIN)SELECT * FROM A,B WHERE A.id=B.id或者SELECT * FROM A INNER JOIN B ON A.id=B.id多表中同時符合某種條件的數據記錄的集合
  • Python_笛卡爾積
    有這麼一個需求,已經有幾個人問過我了,工作中還是經常遇到,看上去很簡單,但是Excel就是不好解決。
  • MySQL 8.0 正式版 8.0.11 發布:比 MySQL 5.7 快 2 倍
    下面簡要介紹 MySQL 8 中值得關注的新特性和改進。1. 性能:MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。MySQL 8.0 在以下方面帶來了更好的性能:讀/寫工作負載、IO 密集型工作負載、以及高競爭("hot spot"熱點競爭問題)工作負載。
  • 【編程碼拉松】笛卡爾積
    笛卡爾(Decartes) 乘積又叫直積。假設集合A={a,b},集合B={0,1,2}, 則兩個集合的笛卡爾積為{(a, 0), (a,1),(a,2),(b,0), (b,1), (b,2)}。可以擴展到多個集合的情況。類似的例子有,如果A表示某學校學生的集合,B表示該學校所有課程的集合,則A與B的笛卡爾積表示所有可能的選課情況。
  • MySQL 8.0 正式版 8.0.11 發布:比 MySQL 5.7 快 2 倍 - OS...
    下面簡要介紹 MySQL 8 中值得關注的新特性和改進。1. 性能:MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。MySQL 8.0 在以下方面帶來了更好的性能:讀/寫工作負載、IO 密集型工作負載、以及高競爭("hot spot"熱點競爭問題)工作負載。
  • mysql 矩陣類型專題及常見問題 - CSDN
    外鍵:外鍵是用於關聯兩個表。持久性是指事務的操作,一旦提交,對於資料庫中數據的改變是永久性的,即使資料庫發生故障也不能丟失已提交事務所完成的改變。bytes極大整數值FLOAT4 bytes單精度 浮點數值DOUBLE8 bytes雙精度 浮點數值DECIMAL對DECIMAL(M,D) ,如果M>D,為M+2否則為D+2小數值字符串類型0-255 bytes定長字符串VARCHAR0-65535 bytes變長字符串TINYBLOB0-255 bytes不超過 255 個字符的二進位字符串TINYTEXT0-255 bytes短文本字符串
  • 【笛卡爾坐標/點積/叉積】圖解高等數學-下 03
    10.1 空間中的笛卡爾(直角)坐標和向量為給空間的點定位, 需要由三條相互垂直的軸. 如下圖所示軸組成右手坐標系空間的點 P 的笛卡爾坐標 (x,y,z) 可用其位置向量表示. 如下圖所示. 笛卡爾坐標也是直角坐標, 因為定義這種坐標的軸以直角相交.
  • MySQL8.0新特性CTE(Common Table Expression)
    CTE(Common Table Expression)可以認為是派生表(derived table)的替代,在一定程度上,CTE簡化了複雜的join查詢和子查詢,提高了SQL的可讀性和執行性能。CTE是ANSI SQL 99標準的一部分,在MySQL 8.0.1版本被引入。原文地址:https://mytecdb.com/blogDetail.php?
  • MySQL 5.7 vs 8.0,哪個性能更牛?
    背景 測試mysql5.7和mysql8.0 分別在讀寫、只讀、只寫模式下不同並發時的性能(tps,qps) 前提 測試使用版本為mysql5.7.22和mysql8.0.15 sysbench
  • 一千行MySQL學習筆記
    MySQL中,可以對InnoDB引擎使用外鍵約束:    語法:    foreign key (外鍵欄位) references 主表名 (關聯欄位) [主表記錄刪除時的動作] [主表記錄更新時的動作]    此時需要檢測一個從表的外鍵需要約束為主表的已存在的值。外鍵在沒有關聯的情況下,可以設置為null.前提是該外鍵列,沒有not null。
  • MySQL8.0窗口函數做數據排名統計詳細教程
    但隨著MySQL8.0中新增了窗口函數之後,針對這類統計就再也不是事了,本文就以常用的排序實例介紹MySQL的窗口函數。 |  6 || 2020010 | mysql   |  75.0 |  7 || 2020009 | mysql   |  70.0 |  8 || 2020006 | mysql   |  60.0 |  9 || 2020002 | mysql   |  50.0 | 10 || 2020007 | mysql   |  50.0 | 11 |+---------+---------+---
  • Java-笛卡爾積實現變量拆分
    笛卡爾積集定義: 設A和B是兩個集合,存在一個集合,它的元素是用A中元素為第一元素,B中元素為第二元素構成的有序二元數組。稱它為集合A和B的笛卡爾積集,記為A×B。即:A×B={(x,y)|x∈A∧y∈B}例如: 集合A中有1和2兩個元素,為:A={1,2};集合B中有a,b,c 三個元素,為B={a,b,c};那麼笛卡爾積運算的結果數量則為6個。
  • 一條查詢SQL在MySQL中是怎麼執行的
    一般連接命令是這樣寫的:mysql -h$ip -P$port -u$user -p輸入命令之後,就需要在交互對話中輸入密碼,密碼也可以直接寫在-p後面,但是這種操作一般是開發過程中,連接生產伺服器不建議這樣做,因為可能會導緻密碼洩露。
  • python3.8操作(插入,刪除)mysql/MariaDB資料庫
    01主題大家好,我是義縣遊學電子科技.今天來跟大家說一個工作中常用到的操作,python3.8操作MariaDB資料庫.因為MariaDB屬於mysql分支因此資料庫命令語句都是通用的非常方便.02環境python-3.8 ,64位mairadb-10.4.7,64位python包:mysql-connector-2.2.9
  • 京東雲正式宣布支持MySQL 8.0
    京東雲正式宣布支持MySQL 8.0 1 月 25 日,京東雲宣布,京東云云資料庫 RDS 在所有地域正式支持 MySQL 8. 0 版本。
  • MySQL 5.7和MySQL 8.0的4個細節差異
    在這些年的MySQL升級需求中,讓我大跌眼鏡的一個現象是:驅動業務從MySQL 5.5升級到MySQL 5.7的很大一個因素是因為JSON這個特性。而讓業務有所顧慮從MySQL 5.7升級到MySQL 8.0的一個主要原因是:驅動版本升級,所以對於MySQL 5.7升級到MySQL 8.0來說,總體的升級動力明顯要低一些,但是規劃的一個優點就是可以把一些工作前置,或者讓它的推行更加順暢,比如我們對於新業務的推行,都是默認按照MySQL 8.0的方案來做。
  • 學個資料庫竟然有笛卡爾,不會還有牛頓吧?
    一、笛卡爾積與內連接萬萬沒有想到,學個資料庫竟然還能接觸到笛卡爾積?後面不會學著學著還會出現牛頓吧……牛頓、拉格朗日、泰勒、傅立葉……簡直就是大學噩夢般的存在。現在有兩張表:部門表、成員表。就需要引入笛卡爾積的概念:格式:select * from member,department;查出來的數據就相當於成員表與部門表的乘積。也就是將成員表裡的每一條數據都和部門表中的每一條匹配連接。
  • 笛卡爾,解析幾何的奠基人
    的笛卡爾。笛卡爾既是一個數學家又是一個哲學家 。可能科學的盡頭是神學。神學部分又不可避免是哲學。哲學所蘊含的強大邏輯又給無數科學發現提供了強大的推理依據。所以笛卡爾是那個時代的推動者。科學史中每一個不經意的發現 都有可能推到人類進步一大截,而或啟迪一群人的發明創造。
  • MySQL 5.0 新特性教程 存儲過程:第四講
    我們使用的是InnoDB,因此外鍵關聯檢查是打開的。然後當我向外鍵表中插入非主鍵表中的值時,動作將會失敗。當然這種條件下可以很快找到錯誤號1216。3.8.DECLARE CONTINUE HANDLER example  mysql> CALL p23()//Query OK, 0 rows affected (0.00 sec)mysql> SELECT @x, @x2//+------+------+| @x | @x2