MySQL中JOIN查詢詳解

2021-05-26 02:18:19 字數 4395 閱讀 9156

一般而言,如果要設計乙個小型資料庫(指**量少),但又要適應海量資料及訪問的效能需求,最有效的方法莫過於針對主要應用場景選擇乙個或幾個效能優異的核心演算法作為引擎,然後努力將一些非主要應用場景作為該演算法的特例或變種植入到引擎當中。

mysql、postgresql 等就是這麼做的。 在 mysql 的 select 查詢當中,其核心演算法就是 join 查詢演算法。其他的查詢語句都相應向 join 靠攏:單錶查詢被當作 join 的特例;子查詢被盡量轉換為 join 查詢……

這裡將從 mysql 5.0 的源**入手,簡要分析 mysql 處理 join 查詢的流程和思路。

下圖是乙個 select sql 傳到 mysql 服務端以後經過的主要函式流程圖。圖上每個小矩形框內代表乙個函式,箭頭的起點為呼叫者,終點為被調函式。箭頭指向乙個大框,則表示呼叫者呼叫了一組函式,順序基本是從上向下。

注:這裡所說的 sql 都不包含 union 子句,因為 mysql 用了單獨的 union 引擎來處理對應  sql,而對於一般開發而言我們也很少使用 union 查詢。 

在上面所有函式中,mysql_excute_command() 函式是 mysql 處理各類 sql 語句的統一入口。sql 語句在經過簡單的語法解析以後,送到這裡,由該函式作進一步分析,並呼叫相應的handle介面作後續處理。對於 select 相關的語句,主要呼叫 handle_select() 和 mysql_select() 兩個函式。

handle_select() 可以處理 select 中含有 union 子句的情況。在不含 union 的簡單結構中,也會直接呼叫 mysql_select() 函式。

mysql_select() 函式就是不帶 union 子句的 select 語句的入口點函式。通常狀況下,每次呼叫以後,它會依次呼叫 join 類的 prepare() 、optimize() 、exec() 三個函式來完成 select 語句的預處理、優化、執行和結果輸出功能。

join::prepare() 是乙個前處理函式。主要進行引數合法性檢查、語法分析並生成更準確的計算機描述、開啟記錄表、子查詢轉換等操作。

join::optimize() 是整個 select 流程的關鍵所在,它負責對前面生成的各種描述結構進行各種優化。優化過程基於大量的規則進行,這些規則我們後面再詳細講述。 join::optimize() 呼叫的一系列函式我們不一一述及,也將各函式內的優化規則總結、概括到後面去一併講解。

join::exec() 也會進行一些執行時優化,這些優化過程會導致實際執行過程與 explain 中顯示的不一致。但大多數情況下,join::exec() 會遵照前面優化的過程執行,因此 join 的流程也基本在 optimize() 中確定。

join::exec() 與 join 最相關的部分是呼叫 do_select() 函式執行取資料的操作。do_select() 會呼叫 sub_select() 函式,該呼叫採用遞迴的方法將兩兩相鄰的表按照依賴關係進行歸併,逐步得到最終的結果集。

結果集返回的操作也在 join::exec() 中執行,或返回到臨時表,或輸入到檔案,或傳送到 socket。這些不是我們關注的重點,因此也一筆帶過。

mysql 優化器的工作是基於規則設計的,如果規則存在缺陷,相應部分的應用也會有一些效能損失。與一些先進的大型資料庫不同,mysql 的這些效能損失可能是永久的(對固定版本而言)。因為大型資料庫在執行過程中會對各種優化結果的執**況進行統計評估以便自動改進後續的執行優化狀況,而 mysql 目前沒有這些功能。因此,了解 mysql 的優化規則,對於較好地設計 sql 語句,提高執行效率有很大的指導意義。

下面列出 mysql 5 在處理select查詢時設計的一些規則。

規則1:如果操作只涉及常表,則去除 distinct 子句;否則如果只有乙個表,在以下情況下會將 distinct 轉為 group by 查詢:

全表掃瞄發生的情況通常為以下兩種:

當 select 語句包含了 limit 子句(這裡和後文提及 limit 子句的時候,預設是沒有 sql_calc_found_rows 子句存在的情況)時,優化器將不使用這一優化規則,因為該情況下優化器將建立臨時表放置 limit 所限制的記錄數,然後返回。

注:limit 子句跟 distinct、group by、order by等子句共存的狀況比較複雜。此時使用 limit 子句除了減少了傳送記錄過程中的耗時以外,通常不應期望有更多的速度提高。因為後面三個子句中的任乙個都可能會使得不管是否存在 limit 子句都要做同樣多,甚至更多一點點的計算。

這裡順便介紹常表的概念。 所謂常表,包括以下型別:

規則2:優化器在以下情況會考慮建立臨時表:

是否要建立臨時表,會在所有表都讀入之前確定。

規則3:盡量將 outer join 轉換為 inner join,並盡可能地巢狀。相應地,on 子句的條件表示式也會被移動到 where 子句。

如果巢狀迴圈join的 where 子句或 on 子句中有乙個條件表示式剔除了內錶中某屬性為 null 的所有值,則  outer join 可以替換為  inner join 。

例如,下面的查詢中:

select * from t1 left join t2 on t2.a=t1.a where t2.b < 5    條件 t2.b < 5 剔除了 null 項,該查詢首先被轉換為:

select * from t1 inner join t2 on t2.a=t1.a where t2.b < 5    然後轉換為等價形式:

select * from t1, t2 on t2.a=t1.a where t2.b < 5 and t2.a=t1.a

類似地,下面的查詢:

select * from t1 left join (t2, t3) on t2.a=t1.a t3.b=t1.b where t2.c < 5    轉化為:

select * from t1, (t2, t3) where t2.c < 5 and t2.a=t1.a t3.b=t1.b

乙個轉換可能會觸發另乙個

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

left join t3 on t3.b=t2.b

where t3 is not null    將轉換為:

select * from t1 left join t2 on t2.a=t1.a, t3

where t3 is not null and t3.b=t2.b    再轉換為:

select * from t1, t2, t3

where t3 is not null and t3.b=t2.b and t2.a=t1.a

規則4:盡量將多個等式轉換連等式。

規則5:order by 操作盡量施加在結果集而不是源集上。

但在 join 操作的 on 子句中有等式或不等式(指不包括"!="在內的其他比較符號)且等式兩邊沒有常數時,可能會先對源集進行排序,然後進行歸併聯接。

規則6:如果某個索引可以獲取所有 select 語句需要的列,則優先考慮該索引。

規則7:盡量將子查詢轉換為 join。

大多數情況下,子查詢可能需要較多的臨時表儲存,並且查詢速度較之 join 要慢得多。

規則8:在允許的情況下,對 join 的各個表重排次序,提高執行的速度。

資料量較小的表可能會被放在最前面先處理,資料量較大的表會稍後處理。但如果 on 子句明確指定了依賴關係,根據依賴關係處理,順序不可調整。

研究mysql源**,本來是期望從理解 mysql 的處理流程中找到一些 sql 優化的新思路。但是,很遺憾,在閱讀 mysql 源**的過程中發現 join 演算法其實是盡量簡單化、核心化。在洋洋灑灑上萬行的**中,join 相關的演算法最核心的只不過若干行基於巢狀迴圈的排序歸併演算法,真正佔了絕大多數的**是在對各種條件進行優化、內外聯結條件轉換、各種子句執行順序的重新 排列等等繁瑣的處理,這些處理的最終目的只有乙個:讓 select sql 能盡快按照 join 的核心演算法執行並輸出結果。另外還有多處的**只是為了某一種特定的 select 語句的優化,有補丁**的性質。

所以這樣看來,研究 mysql 源**可以對一些問題獲得比較確切的答案,但很難據此找到脫離於資料庫理論之外的其他措施來大幅提高資料庫查詢的效能。mysql join 演算法的思路和速度看起來都不錯,即便是 oracle 之類的大型資料庫的 join 演算法在速度方面未必能有多少的提高。oracle 在 cbo 下的 hash join 在兩表大小相差很大的情況下會有較好的效能表現,但其使用面較窄,對於小型資料庫而言,不是首要發展方向(mysql 的 index join 過程中也使用了類似 hash join 的演算法,但未作重點發展)。

儘管如此,從**分析裡面還是可以獲得一些小技巧,從而充分利用mysql的核心演算法優勢,同時避開其缺點。

MySQL的Join查詢詳解

create table t1 id int 11 notnull a int 11 default null b int 11 default null primary key id key a a engine innodb t1裡插入100條資料 create table t2 id int ...

mysql多表查詢方法(join)

表a記錄如下 aid anum 1 a20050111 2 a20050112 3 a20050113 4 a20050114 5 a20050115 表b記錄如下 bid bname 1 2006032401 2 2006032402 3 2006032403 4 2006032404 8 200...

mysql優化子查詢 join

使用連線 join 來代替子查詢 sub queries mysql從4.1開始支援sql的子查詢。這個技術可以使用select語句來建立乙個單列的查詢結果,然後把這個結果作為過濾條件用在另乙個查詢中。例如,我們要將客戶基本資訊表中沒有任何訂單的客戶刪除掉,就可以利用子查詢先從銷售資訊表中將所有發出...