神奇的 SQL 之 WHERE 條件的提取與應用

開心一刻

  小明:爲何中國人結婚非要選一個好日子呢 ?數據庫

  樓主:嗯 ? 那確定啊,結完婚以後你還能有好日子嗎 ?數據結構

  小明:那結婚時所說的白頭到總是真的嗎 ?ide

  樓主:這哪能是真的,你看如今,頭髮還沒白就禿了post

  小明:那女生的公主病是怎麼回事 ?優化

  樓主:緣由很簡單,不是長得醜就是窮ui

  小明:那又漂亮又有錢的呢 ?spa

  樓主:別逗了,那不是公主病,那是真公主 !3d

  小明:那你的是有公主病,仍是真公主 ?netty

  樓主:別鬧了,個人在硬盤裏code

問題描述

  一條 SQL 在數據庫中是如何執行的呢 ?相信不少人都會對這個問題比較感興趣。可是,感興趣歸感興趣,你得去追呀,還臆想着她主動到你懷裏來 ?

  一條 SQL 在數據庫中的生命週期涵蓋了 SQL 的詞法解析、語法解析、權限檢查、查詢優化、SQL執行等一系列的步驟,是一個至關複雜的過程,不亞於你追她的艱苦歷程,不是隻言片語就說的完的。可是,你們先別緊張,上面說的那些了,今天一個也不講,氣不氣 ?

  今天和你們一塊兒來看一下 SQL 生命週期中比較有意思的一個環節

給定一條 SQL,如何提取其中的 where 條件 ?

where 條件中的每一個子條件,在 SQL 執行的過程當中有分別起着什麼樣的做用 ?

前提準備

  正式開講以前了,咱們先來回顧一些內容

  SQL 執行流程

    這是 MySQL 數據庫中 SQL 的執行流程,其餘數據庫應該相似

  關係型數據庫中的數據組織

    關係型數據庫中,數據組織涉及到兩個最基本的結構:表與索引。表中存儲的是完整數據記錄,分爲堆表和聚簇索引表;堆表中全部的記錄無序存儲,聚簇索引表中全部的記錄則是按照記錄主鍵進行排序存儲。索引中存儲的是完整記錄的一個子集,用於加速記錄的查詢速度,索引的組織形式,通常均爲B+樹結構

    MySQL 的 InnoDB 採用的是聚簇索引表,數據記錄和索引是一塊兒存儲的,相似以下

    InnoDB 二級索引(非聚簇索引)的結構與彙集索引的結構基本相同,只是葉子節點有些許差異,二級索引的葉子節點存的是索引值 + 主鍵值,而聚簇索引的葉子節點存的是索引值 + 完整的數據記錄,因此經過二級索引查找的過程是先找到該索引值對應的彙集索引的值,而後再經過該聚簇索引值到聚簇索引樹上查找對應的完整數據記錄,這個過程稱爲回表!固然也有不須要回表的狀況,這裏就不展開了

    Oracle、DB二、PostgreSQL,MySQL 的 MyISAM 引擎,採用的是堆表形式來存儲數據,索引和數據是分開存儲的,相似以下

    堆表結構中的聚簇索引和二級索引基本就沒什麼區別了,能夠簡單的認爲聚簇索引的結構和二級索引中的惟一索引的結構是同樣的

    其實表結構採用何種形式並不重要,由於下面講的內容在任何表結構中均適用

WHERE 條件的提取

  建表 tbl_test 並初始化數據

create table tbl_test (a int primary key, b int, c int, d int, e varchar(50));
create index idx_bcd on tbl_test(b, c, d);
insert into tbl_test values (4,3,1,1,'a');
insert into tbl_test values (1,1,1,2,'d');
insert into tbl_test values (8,8,7,8,'h');
insert into tbl_test values (2,2,1,2,'g');
insert into tbl_test values (5,2,2,5,'e');
insert into tbl_test values (3,3,2,1,'c');
insert into tbl_test values (7,4,0,5,'b');
insert into tbl_test values (6,5,2,4,'f');
View Code

  假設數據數據結構是堆表形式,那麼 idx_bcd 索引的結構圖大體以下(聚簇索引不同,類比一下應該能夠畫出來,我就偷個懶不畫了)

  組合索引 idx_bcd 上有 b,c,d 三個字段,不包括 a,e 字段,它是先按照 b 字段排序,b 字段相同,則按照 c 字段排序,以此類推

  針對上表,咱們分析下 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a'; 此 SQL 中 WHERE 條件用到了 b,c,d,e 四個字段,而索引 idx_bcd 恰好是創建在 b,c,d 三個字段上,那麼走 idx_bcd 索引進行條件過濾應該能提升查詢效率,既然走 idx_bcd 索引進行條件過濾,那麼咱們來思考下如下幾個關鍵問題

  三個關鍵問題

    一、上述 SQL,覆蓋了 idx_bcd 索引的哪一個範圍 ?     

      起始點由 b >= 2,c > 0 決定,因此 2,1,2 是第一個須要檢查的索引項

      終止點由 b < 7 決定,因此 8,7,8 是第一個不須要檢查的索引項, 8,7,8 後面的也無需檢索

    二、範圍肯定後,SQL 中還有哪些條件可使用 idx_bcd 索引來過濾 ?

      上面咱們已經確認了範圍 2,1,2 ~ 8,7,8 ,那麼在這個範圍內的每個索引項是否是都知足 WHERE 條件了 ? 很顯然不是, 4,0,5 不知足 c > 0 , 2,1,2 不知足 d != 2 ;因此 c,d 列的 where 條件能夠經過索引 idx_bcd 來過濾

    三、當 idx_bcd 索引物盡其用後,還有哪些條件是沒法經過 idx_bcd 索引過濾的 ?

      這個很明顯, e != 'a' 沒法在索引 idx_bcd 上進行過濾,由於索引並未包含 e 列;e 列只在堆表上存在,因此須要將已經知足索引查詢條件的記錄回表,取出對應的完整數據記錄,而後看該數據記錄中 e 列值是否知足 e != 'a' 條件

  有些小夥伴可能以爲上述 WHERE 條件的抽取具備特殊性,不具廣泛性,那麼咱們抽象出一套放置於全部 SQL 語句皆準的 WHERE 查詢條件的提取規則:Index Key (First Key & Last Key),Index Filter,Table Filter,咱們們往下仔細看

  Index Key

    用於肯定 SQL 查詢在索引中的連續範圍(起始點 + 終止點)的查詢條件,被稱之爲Index Key;因爲一個範圍,至少包含一個起始條件與一個終止條件,所以 Index Key 也被拆分爲 Index First Key 和 Index Last Key,分別用於定位索引查找的起始點以終止點

    Index First Key

    用於肯定索引查詢範圍的起始點;提取規則:從索引的第一個鍵值開始,檢查其在 where 條件中是否存在,若存在而且條件是 =、>=,則將對應的條件加入Index First Key之中,繼續讀取索引的下一個鍵值,使用一樣的提取規則;若存在而且條件是 >,則將對應的條件加入 Index First Key 中,同時終止 Index First Key 的提取;若不存在,一樣終止 Index First Key 的提取

    針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index First Key 爲 b >= 2, c > 0 ,因爲 c 的條件爲 >,提取結束

    Index Last Key

    用於肯定索引查詢範圍的終止點,與 Index First Key 正好相反;提取規則:從索引的第一個鍵值開始,檢查其在 where 條件中是否存在,若存在而且條件是 =、<=,則將對應條件加入到 Index Last Key 中,繼續提取索引的下一個鍵值,使用一樣的提取規則;若存在而且條件是 < ,則將條件加入到 Index Last Key 中,同時終止提取;若不存在,一樣終止Index Last Key的提取

    針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index Last Key爲 b < 7 ,因爲是 < 符號,提取結束

  Index Filter

    在完成 Index Key 的提取以後,咱們根據 where 條件固定了索引的查詢範圍,那麼是否是在範圍內的每個索引項都知足 WHERE 條件了 ? 很明顯 4,0,5 , 2,1,2 均屬於範圍中,可是又均不知足SQL 的查詢條件

    因此 Index Filter 用於索引範圍肯定後,肯定 SQL 中還有哪些條件可使用索引來過濾;提取規則:從索引列的第一列開始,檢查其在 where 條件中是否存在,若存在而且 where 條件僅爲 =,則跳過第一列繼續檢查索引下一列,下一索引列採起與索引第一列一樣的提取規則;若 where 條件爲 >=、>、<、<= 其中的幾種,則跳過索引第一列,將其他 where 條件中索引相關列所有加入到 Index Filter 之中;若索引第一列的 where 條件包含 =、>=、>、<、<= 以外的條件,則將此條件以及其他 where 條件中索引相關列所有加入到 Index Filter 之中;若第一列不包含查詢條件,則將全部索引相關條件均加入到 Index Filter之中

    針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index Filter 爲 c > 0 and d != 2 ,由於索引第一列只包含 >=、< 兩個條件,所以第一列跳過,將餘下的 c、d 兩列加入到 Index Filter 中,提取結束

  Table Filter

    這個就比較簡單了,where 中不能被索引過濾的條件都歸爲此中;提取規則:全部不屬於索引列的查詢條件,均歸爲 Table Filter 之中

    針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,那麼 Table Filter 就爲  e != 'a' 

  是否是有點感受了 ? 相信此刻,你們對 where 條件的提取基本清楚了,但怎麼應用了 ?

WHERE 條件的應用

  SQL 語句中的 where 條件,最終都會被提取到 Index Key (First Key & Last Key),Index Filter 與 Table Filter 之中,那麼 where 條件的應用,其實就是 Index Key (First Key & Last Key),Index Filter 與Table Filter 的應用

  Index First Key,只是用來定位索引的起始點,所以只在索引第一次Search Path(沿着索引B+樹的根節點一直遍歷,到索引正確的葉節點位置)時使用,只會判斷一次

  Index Last Key,用來定位索引的終止點,所以對於起始點以後讀到的每一條索引記錄,均須要判斷是否知足 Index Last Key,若不知足,則當前查詢結束

  Index Filter,用於過濾索引範圍中不知足條件的索引項,所以對於索引範圍中的每一條索引項,均須要與 Index Filter 進行匹對,若不知足 Index Filter 則直接丟棄,繼續讀取索引下一條記錄

  Table Filter,用於過濾不能被索引過濾的條件,此時的索引項已經知足了 Index First Key 與 Index Last Key 構成的範圍,而且知足 Index Filter 的條件,可是索引項沒法過濾 Table Filter 中的條件,因此回表讀取完整的數據記錄,判斷完整記錄是否知足 Table Filter 中的查詢條件,若不知足,跳過當前記錄,繼續讀取索引項的下一條索引項,若知足,則返回記錄,此記錄知足了 where 的全部條件,能夠返回給客戶端

總結

  一、SQL 語句中的 where 條件,最終都會被提取到 Index Key (First Key & Last Key),Index Filter 與 Table Filter ,提取規則須要你們好好體會下

  二、數據庫中 where 條件的過濾是 one by one(一條一條)的方式進行的,聯表查詢其實也是 one by one 的方式進行的;雖然咱們在開發中感受到不是 one by one,那實際上是數據庫驅動作了處理

  三、Index Key 的提取,須要考慮到間隙鎖,避免幻讀問題,有興趣的小夥伴能夠去琢磨下

  四、MySQL 5.6 中引入的 Index Condition Pushdown,到底是 Push Down 了什麼,從哪 Push Down 到哪 ? 你們能夠先去了解下,咱們下篇詳細講解

參考

  SQL中的where條件,在數據庫中提取與應用淺析

  MySQL的索引

  MySQL的server層和存儲引擎層是如何交互的

相關文章
相關標籤/搜索