select語句中where條件的提取過程
孔個個
依然,在整理where條件提取過程時,發現中文互聯網上關於這一塊的知識要么是存在錯誤自相矛盾的,要么是版本過老,遂自己整理了一版。
在驗證這些內容的過程中走了很多彎路,而搞懂后便豁然開朗,這部分都寫在文章中了,多數以注解的形式存在着。
所有SQL的where條件,均可歸納為3大類:
- Index Key (First Key & Last Key):用於確定SQL查詢在索引中的連續范圍(起始范圍+結束范圍)的查詢條件,被稱之為Index Key。
- Index Filter:經過index key提取的結果里,並不都是滿足查詢條件的項,因此需要進一步對索引列進行篩選。
- Table Filter:所有不屬於索引列的查詢條件,均歸為Table Filter之中。
嘗試總結一下:
首先,引擎層通過index key將連續范圍的索引列條件提取走。就是屬於索引列的查詢條件里,以=、>=的,以及第一個>的條件作為范圍開始(index first key),直到=、<=、第一個<的條件作為范圍結束(index last key)。
其次,server層通過index filter將第一列滿足index key之外的所有屬於索引列查詢條件的,都提取走。
最后,server層將剩下的所有不屬於索引列查詢條件的,提取走。
-
Index key (First Key & Last Key)
-
引擎層
-
用於確定SQL查詢在索引中的連續范圍(起始范圍+結束范圍)的查詢條件,被稱之為Index Key
-
Index First Key
-
用於確定索引查詢的起始范圍。
-
提取規則:
- 從索引的第一個column開始,檢查其在where條件中是否存在。
- 若存在並且條件是=、>=,則將對應的條件加入Index First Key之中,繼續讀取索引的下一個鍵值,使用同樣的提取規則;
- 若存在並且條件是>,則將對應的條件加入Index First Key中,同時終止Index First Key的提取;
- 若不存在,同樣終止Index First Key的提取。
=、>=一定加入index first key, 其次,第一個> 的條件會加入index first key, 然后不管有沒有第一個> 條件,都結束提取。
也就是說,index first key包含着: 等值,包含自身的范圍起始值,以及[第一個]不包含自身的范圍起始值的條件
- 從索引的第一個column開始,檢查其在where條件中是否存在。
-
-
Index Last Key
-
用於確定索引查詢的終止范圍。
-
提取規則:
- 從索引的第一個鍵值開始,檢查其在where條件中是否存在。
- 若存在並且條件是=、<=,則將對應條件加入到Index Last Key中,繼續提取索引的下一個鍵值,使用同樣的提取規則;
- 若存在並且條件是 < ,則將條件加入到Index Last Key中,同時終止提取;
- 若不存在,同樣終止Index Last Key的提取。
和index first key相對,也就是說,index last key包含着: 等值,包含自身的范圍結束值,以及[第一個]不包含自身的范圍結束值。
- 從索引的第一個鍵值開始,檢查其在where條件中是否存在。
-
-
-
Index Filter
-
Server層。(ICP特性作用時,Index Filter下降(推)到引擎層實現。)
-
index key之后,此范圍中的項並不都是滿足查詢條件的項,對范圍之內的數據進行再次條件限制,稱為 Index Filter。 其實就是,有索引的列除了被index key連續范圍條件提取了之外,其他的都給index filter。
-
Index Filter提取規則
-
同樣從索引列的第一column開始,檢查其在where條件中是否存在。
-
若存在並且where條件僅為 =,則跳過第一列繼續檢查索引下一列,下一索引列采取與索引第一列同樣的提取規則;
索引第一列條件為等值則跳過這一列,因為這個被index key拿走啦
-
若where條件為 >=、>、<、<= 其中的幾種,則跳過索引第一列,將其余where條件中索引相關列全部加入到Index Filter之中(這句很重要,因為這一塊在index key提取環節中,非第一列的鍵也可能符合index key條件被提取,到了index filter又因為這個規則歸給inex filter了!);
索引第一列條件為范圍,則跳過這一列,因為這個被index key拿走啦,然后把其他所有[有索引的]列的條件都拿過來
-
若索引第一列的where條件包含 =、>=、>、<、<= 之外的條件,則將此條件以及其余where條件中索引相關列全部加入到Index Filter之中;
索引第一列條件非范圍非等值,意味着沒有東西被index key拿走,自然就把所有[有索引的]列的條件都拿到這里了。
-
若第一列不包含查詢條件,則將所有索引相關條件均加入到Index Filter之中。(同上)
-
-
-
Table Filter
- Server層
- 無索引的條件,所有不屬於索引列的查詢條件,均歸為Table Filter之中。
Index Key/Index Filter/Table Filter小結
- SQL語句中的where條件,使用以上的提取規則,最終都會被提取到Index Key (First Key & Last Key),Index Filter與Table Filter之中。
- Index First Key,只是用來定位索引的起始范圍,因此只在索引第一次Search Path(沿着索引B+樹的根節點一直遍歷,到索引正確的葉節點位置)時使用,一次判斷即可;
- Index Last Key,用來定位索引的終止范圍,因此對於起始范圍之后讀到的每一條索引記錄,均需要判斷是否已經超過了Index。
- Table Filter,則是最后一道where條件的防線,用於過濾通過前面索引的層層考驗的記錄,此時的記錄已經滿足了Index First Key與Index Last Key構成的范圍,並且滿足Index Filter的條件,回表讀取了完整的記錄,判斷完整記錄是否滿足Table Filter中的查詢條件,同樣的,若不滿足,跳過當前記錄,繼續讀取索引的下一條記錄,若滿足,則返回記錄,此記錄滿足了where的所有條件,可以返回給前端用戶。
ICP特性:MySQL 5.6中引入的Index Condition Pushdown,將Index Filter環節 Push Down到索引層面進行過濾。
- 在MySQL 5.6之前,並不區分Index Filter與Table Filter,統統將Index First Key與Index Last Key范圍內的索引記錄,回表讀取完整記錄,然后返回給MySQL Server層進行過濾。
- 在MySQL 5.6之后,Index Filter與Table Filter分離,Index Filter下降到InnoDB的索引層面進行過濾,減少了回表與返回MySQL Server層的記錄交互開銷,提高了SQL的執行效率。
SQL的where條件提取的例子
考慮以下的一條SQL:
create table t1 (
a int,
b int,
c int,
d int,
e varchar(20),
primary key (a),
key idx_t1_bcd (b,c,d)
);
mysql> select * from t1;
+---+------+------+------+------+
| a | b | c | d | e |
+---+------+------+------+------+
| 1 | 1 | 1 | 1 | a |
| 3 | 3 | 2 | 2 | c |
| 4 | 3 | 1 | 1 | d |
| 5 | 2 | 3 | 5 | e |
| 6 | 6 | 4 | 4 | f |
| 7 | 4 | 5 | 5 | g |
+---+------+------+------+------+
select * from t1
where b >= 2 // idx_t1_bcd 索引第一列b
and b < 8 // idx_t1_bcd 索引第一列b
and c > 1 // idx_t1_bcd 索引第二列c
and d != 4 // idx_t1_bcd 索引第三列d
and e != ‘a’; // 不屬於索引列
-
可以發現where條件使用到了[b,c,d,e]四個字段,而t1表的idx_t1_bcd索引,恰好使用了[b,c,d]這三個字段。走idx_t1_bcd索引進行條件過濾,應該是一個不錯的選擇。
-
此SQL,覆蓋索引idx_t1_bcd上的哪個范圍?
-
起始范圍:記錄[2,2,2]是第一個需要檢查的索引項。索引起始查找范圍由b >= 2,c > 1決定。
-
終止范圍:記錄[8,8,8]是第一個不需要檢查的記錄,而之前的記錄均需要判斷。索引的終止查找范圍由b < 8決定;
-
-
在確定了查詢的起始、終止范圍之后,SQL中還有可以通過索引idx_t1_bcd,使用c > 1 and d != 4條件進行索引記錄的過濾。
- 根據SQL,固定了索引的查詢范圍[(2,2,2),(8,8,8))之后,此索引范圍中並不是每條記錄都是滿足where查詢條件的。例如:(3,1,1)不滿足c > 1的約束;(6,4,4)不滿足d != 4的約束。而c,d列,均可在索引idx_t1_bcd中過濾掉不滿足條件的索引記錄的。
- 因此,SQL中還可以使用c > 1 and d != 4條件進行索引記錄的過濾。
-
在確定了索引中最終能夠過濾掉的條件之后,e != ‘a’這個查詢條件,是無法在索引idx_t1_bcd上進行過濾的,因為索引並未包含e列。e列只在表上存在。為了過濾此查詢條件,必須將已經滿足索引查詢條件的記錄回表,取出表中的e列,然后使用e列的查詢條件e != ‘a’進行最終的過濾。
-
-
剖析一下where條件提取流程:
-
Index Key (First Key & Last Key)
- 應用index first key提取規則,提取出來的Index First Key為(b >= 2, c > 1)。由於c的條件為 >,提取結束,不包括d。
- 應用Index Last Key提取規則,提取出來的Index Last Key為(b < 8),由於是 < 符號,因此提取b之后結束。
-
Index Filter
在上面的SQL用例中,(3,1,1),(6,4,4)均屬於范圍中,但是又均不滿足SQL的查詢條件。
- 針對上面的用例SQL,索引第一列只包含 >=、< 兩個條件(b>=2, b<8),因此第一列可跳過,將余下的c、d兩列加入到Index Filter中。因此獲得的Index Filter為 c > 1 and d != 4 。
-
Table Filter
- 所有不屬於索引列的查詢條件,均歸為Table Filter之中。Table Filter就為 e != 'a'
-
SQL執行過程中的優化特性/算法
-
ICP特性將Index Filter下降到InnoDB的索引層面進行過濾,減少了IO次數,減少了回表與返回MySQL Server層的記錄交互開銷,提高了SQL的執行效率。
-
是基於輔助/第二索引的查詢,減少隨機IO,將隨機IO轉化為順序IO,提高查詢效率。
查詢輔助索引時,首先把查詢結果按照主鍵進行排序,按照主鍵的順序進行書簽查找,避免頻繁發生離散讀操作導致緩沖區中的頁被替換出緩沖區,然后又不斷的被新的請求讀入緩沖區,減少緩沖池中頁被替換的次數。
-
將驅動表/外部表的結果集作為循環基礎數據,然后循環該結果集,每次獲取一條數據作為下一個表的過濾條件查詢數據,然后合並結果,獲取結果集返回給客戶端。
Nested-Loop一次只將一行傳入內層循環, 所以外層循環(的結果集)有多少行, 內存循環便要執行多少次,效率非常差。
-
主要用於當被join的表上無索引。
將外層循環的行/結果集存入join buffer, 內層循環的每一行與整個buffer中的記錄做比較,從而減少內層循環的次數。
-
當被join的表能夠使用索引時,就先排好順序,然后再去檢索被join的表。對這些行按照索引字段進行排序,因此減少了隨機IO。
如果被Join的表上沒有索引,則使用老版本的BNL策略(Block Nested-Loop)。