mysql的聯合索引最左匹配原則問題

MySQL的聯合索引

聯合索引的最左匹配原則會一直向右匹配直到遇到范圍查詢(>、<、between、like) 就會停止匹配。

這個結論并不全對!去掉 「between 和 like 」這個結論就沒問題了

經過實驗的證明,我得出的結論是這樣的:

聯合索引的最左匹配原則,在遇到范圍查詢(如 >、<)的時候,就會停止匹配,也就是范圍查詢的字段可以用到聯合索引,但是在范圍查詢字段后面的字段無法用到聯合索引。但是,對于 >=、<=、BETWEEN、like 前綴匹配這四種范圍查詢,并不會停止匹配。
用幾個實驗例子來說明這個結論:

B+Tree 索引

首先,先來認識下 B+Tree 索引。

MySQL 的 InnoDB 存儲引擎會為每一張數據庫表創建一個「聚簇索引」來保存表的數據,聚簇索引默認使用的是B+Tree 索引

為了讓大家理解 B+Tree 索引的存儲和查詢的過程,接下來我通過一個簡單例子,說明一下 B+Tree 索引在存儲數據中的具體實現。

假設有一張商品表,表里有這些數據:

這些數據,存儲在 B+Tree 索引時是長什么樣子的?

B+Tree 是一種多叉樹,葉子節點才存放數據,非葉子節點只存放索引,而且每個節點里的數據是按主鍵值(id)順序存放的,每一層父節點的索引值都會出現在下層子節點的索引值中,因此在葉子節點中,包括了所有的索引值信息,并且每一個葉子節點都指向下一個葉子節點,形成一個鏈表,便于范圍查詢。

聚簇索引的 B+Tree 如圖所示:

假設,執行了 ?select * from t_product where id = 5查詢語句,該查詢語句的條件是找到 id(主鍵)為 5 的這條記錄。因為 B+Tree 是一個有序的數據結構,所以可以通過二分查找算法快速定位到這條記錄,這也就是我們常說的索引查詢,具體過程如下:

  • 從根節點開始,將 5 與根節點的索引數據 (1,10,20) 比較,5 在 1 和 10 之間,根據二分查找算法,找到第二層的索引數據 (1,4,7);
  • 在第二層的索引數據 (1,4,7)中進行查找,因為 5 在 4 和 7 之間,根據二分查找算法,找到第三層的索引數據(4,5,6);
  • 在葉子節點的索引數據(4,5,6)中進行查找,然后我們找到了索引值為 5 的這條記錄。

聚簇索引只能用于主鍵字段的快速查詢,如果想實現「非主鍵字段」的快速查詢,我們就要針對「非主鍵字段」創建索引,這種索引稱作為「二級索引」。二級索引同樣基于 B+Tree 實現的,不過二級索引的葉子節點存放的是主鍵值,不是實際數據。

我這里將前面的商品表中的 product_no (商品編碼)字段設置為二級索引,那么二級索引的 B+Tree 如下圖,其中非葉子的索引值是 product_no(圖中橙色部分),葉子節點存儲的數據是主鍵值(圖中綠色部分)。

如果我用 product_no 二級索引查詢商品,如下查詢語句:

select * from product where product_no = '0002';

會先在二級索引的 B+Tree 中快速查找到 product_no 為 0002 的二級索引記錄,然后獲取主鍵值,然后利用主鍵值在主鍵索引的 B+Tree 中快速查詢到對應的葉子節點,然后獲取完整的記錄。這個過程叫「回表」,也就是說要查兩個 B+Tree 才能查到數據。如下圖:

不過,當查詢的數據是能在二級索引的 B+Tree 的葉子節點里查詢到,這時就不用再查主鍵索引查,比如下面這條查詢語句:

select id from product where product_no = '0002';

這種在二級索引的 B+Tree 就能查詢到結果的過程就叫作「覆蓋索引」,也就是只需要查一個 B+Tree 就能找到數據。

什么是聯合索引?

前文我將 product_no 字段設置為了索引,這種二級索引只有一個字段。如果將多個字段組合成一個索引,那么這種二級索引就被稱為聯合索引。

比如,將商品表中的 product_no 和 name 字段組合成聯合索引`(product_no, name)``,創建聯合索引的方式如下:

CREATE INDEX index_product_no_name ON product(product_no, name);

聯合索引 ``(product_no, name)` 的 B+Tree 示意圖如下:

可以看到,聯合索引的非葉子節點用兩個字段的值作為 B+Tree 的索引值。

聯合索引的 B+Tree 是先按 product_no 進行排序,然后再 product_no 相同的情況再按 name 字段排序記住這句話,很重要!

最左匹配原則

使用聯合索引時,存在最左匹配原則,也就是按照最左優先的方式進行索引的匹配。

在使用聯合索引進行查詢的時候,如果不遵循「最左匹配原則」,聯合索引會失效,這樣就無法利用到索引快速查詢的特性了。

比如,如果創建了一個 (a, b, c) 聯合索引,如果查詢條件是以下這幾種,就可以利用聯合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

需要注意的是,因為有查詢優化器,所以 a 字段在 where 子句的順序并不重要。但是,如果查詢條件是以下這幾種,因為不符合最左匹配原則,所以就無法匹配上聯合索引,聯合索引就會失效:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

上面這些查詢條件之所以會失效,是因為(a, b, c) 聯合索引,是先按 a 排序,在 a 相同的情況再按 b 排序,在 b 相同的情況再按 c 排序。所以,b 和 c 是全局無序,局部相對有序的,這樣在沒有遵循最左匹配原則的情況下,是無法利用到索引的

我這里舉聯合索引(a,b)的例子,該聯合索引的 B+ Tree 如下:

可以看到,a 是全局有序的(1, 2, 2, 3, 4, 5, 6, 7 ,8),而 b 是全局是無序的(12,7,8,2,3,8,10,5,2)。因此,直接執行 where b = 2 這種查詢條件沒有辦法利用聯合索引的,利用索引的前提是索引里的 key 是有序的。

只有在 a 相同的情況才,b 才是有序的,比如 a 等于 2 的時候,b 的值為(7,8),這時就是有序的,這個有序狀態是局部的,因此,執行 where a = 2 and b = 7 這種查詢條件時, a 和 b 字段能用到聯合索引的,也就是聯合索引生效了。

聯合索引范圍查詢


聯合索引有一些特殊情況,并不是查詢過程使用了聯合索引查詢,就代表聯合索引中的所有字段都用到了聯合索引進行索引查詢,也就是可能存在部分字段用到聯合索引的 B+Tree,部分字段沒有用到聯合索引的 B+Tree 的情況。

這種特殊情況就發生在范圍查詢。也就是文章開頭的那句話:聯合索引的最左匹配原則會一直向右匹配直到遇到「范圍查詢」就會停止匹配。也就是范圍查詢的字段可以用到聯合索引,但是范圍查詢字段的后面的字段無法用到聯合索引。

范圍查詢有很多種,那到底是哪些范圍查詢會導致聯合索引的最左匹配原則會停止匹配呢?

接下來,舉例幾個范圍查詢的例子,下面的實驗案例是基于 MySQL 8.0 做的。

例子一

Q1: select * from t_table where a > 1 and b = 2,聯合索引(a, b)哪一個字段用到了聯合索引的 B+Tree?

由于聯合索引(二級索引)是先按照 a 字段的值排序的,所以符合 a > 1 條件的二級索引記錄肯定是相鄰的,于是在進行索引掃描的時候,可以定位到符合 a > 1 條件的第一條記錄,然后沿著記錄所在的鏈表向后掃描,直到某條記錄不符合 a > 1 條件位置。所以 a 字段可以在聯合索引的 B+Tree 中進行索引查詢。

但是在符合 a > 1 條件的二級索引記錄的范圍里,b 字段的值是無序的。

比如,下圖的聯合索引的 B+ Tree 里:

下面這三條記錄的 a 字段的值都符合 a > 1 查詢條件,而 b 字段的值是無序的:

  • a 字段值為 5 的記錄,該記錄的 b 字段值為 8;
  • a 字段值為 6 的記錄,該記錄的 b 字段值為 10;
  • a 字段值為 7 的記錄,該記錄的 b 字段值為 5;

因此,我們不能根據查詢條件 b = 2 來進一步減少需要掃描的記錄數量(b 字段無法利用聯合索引進行索引查詢的意思)。

所以在執行 Q1 這條查詢語句的時候,對應的掃描區間是 (2, + ∞),形成該掃描區間的邊界條件是 a > 1,與 b = 2 無關。

因此,Q1 這條查詢語句只有 a 字段用到了聯合索引進行索引查詢,而 b 字段并沒有使用到聯合索引。

我們也可以在執行計劃中的 key_len 知道這一點,在使用聯合索引進行查詢的時候,通過 key_len 我們可以知道優化器具體使用了多少個字段的查詢條件來形成掃描區間的邊界條件。

舉例個例子 ,a 和 b 都是 int 類型且不為 NULL 的字段,那么 Q1 這條查詢語句執行計劃如下:

可以看到 key_len 為 4 字節(如果字段允許為 NULL,就在字段類型占用的字節數上加 1,也就是 5 字節),說明只有 a 字段用到了聯合索引進行索引查詢,而且可以看到,即使 b 字段沒用到聯合索引,key 為 idx_a_b,說明 Q1 查詢語句使用了 idx_a_b 聯合索引。

通過 Q1 查詢語句我們可以知道,a 字段使用了 > 進行范圍查詢,聯合索引的最左匹配原則在遇到 a 字段的范圍查詢( >)后就停止匹配了,因此 b 字段并沒有使用到聯合索引。

例子二

Q2: select * from t_table where a >= 1 and b = 2,聯合索引(a, b)哪一個字段用到了聯合索引的 B+Tree?

Q2 和 Q1 的查詢語句很像,唯一的區別就是 a 字段的查詢條件「大于等于」。

由于聯合索引(二級索引)是先按照 a 字段的值排序的,所以符合 >= 1 條件的二級索引記錄肯定是相鄰,于是在進行索引掃描的時候,可以定位到符合 >= 1 條件的第一條記錄,然后沿著記錄所在的鏈表向后掃描,直到某條記錄不符合 a>= 1 條件位置。所以 a 字段可以在聯合索引的 B+Tree 中進行索引查詢。

雖然在符合 a>= 1 條件的二級索引記錄的范圍里,b 字段的值是「無序」的,但是對于符合 a = 1 的二級索引記錄的范圍里,b 字段的值是「有序」的(因為對于聯合索引,是先按照 a 字段的值排序,然后在 a 字段的值相同的情況下,再按照 b 字段的值進行排序)。

于是,在確定需要掃描的二級索引的范圍時,當二級索引記錄的 a 字段值為 1 時,可以通過 b = 2 條件減少需要掃描的二級索引記錄范圍(b 字段可以利用聯合索引進行索引查詢的意思)。也就是說,從符合 a = 1 and b = 2 條件的第一條記錄開始掃描,而不需要從第一個 a 字段值為 1 的記錄開始掃描。

所以,Q2 這條查詢語句 a 和 b 字段都用到了聯合索引進行索引查詢。

我們也可以在執行計劃中的 key_len 知道這一點。執行計劃如下:

可以看到 key_len 為 8 字節,說明優化器使用了 2 個字段的查詢條件來形成掃描區間的邊界條件,也就是 a 和 b 字段都用到了聯合索引進行索引查詢。

通過 Q2 查詢語句我們可以知道,雖然 a 字段使用了 >= 進行范圍查詢,但是聯合索引的最左匹配原則并沒有在遇到 a 字段的范圍查詢( >=)后就停止匹配了,b 字段還是可以用到了聯合索引的。

例子三

Q3: SELECT * FROM t_table WHERE a BETWEEN 2 AND 8 AND b = 2,聯合索引(a, b)哪一個字段用到了聯合索引的 B+Tree?

Q3 查詢條件中 a BETWEEN 2 AND 8 的意思是查詢 a 字段的值在 2 和 8 之間的記錄。

不同的數據庫對 BETWEEN ... AND 處理方式是有差異的。在 MySQL 中,BETWEEN 包含了 value1 和 value2 邊界值,類似于 >= and =<。而有的數據庫則不包含 value1 和 value2 邊界值(類似于 > and <)。

這里我們只討論 MySQL。由于 MySQL 的 BETWEEN 包含 value1 和 value2 邊界值,所以類似于 Q2 查詢語句,因此Q3 這條查詢語句 a 和 b 字段都用到了聯合索引進行索引查詢。

我們也可以在執行計劃中的 key_len 知道這一點。執行計劃如下:

可以看到 key_len 為 8 字節,說明優化器使用了 2 個字段的查詢條件來形成掃描區間的邊界條件,也就是 a 和 b 字段都用到了聯合索引進行索引查詢。

通過 Q3 查詢語句我們可以知道,雖然 a 字段使用了 BETWEEN 進行范圍查詢,但是聯合索引的最左匹配原則并沒有在遇到 a 字段的范圍查詢( BETWEEN)后就停止匹配了,b 字段還是可以用到了聯合索引的。

例子四

Q4: SELECT * FROM t_user WHERE name like 'j%' and age = 22,聯合索引(name, age)哪一個字段用到了聯合索引的 B+Tree?

由于聯合索引(二級索引)是先按照 name 字段的值排序的,所以前綴為 ‘j’ 的 name 字段的二級索引記錄都是相鄰的, 于是在進行索引掃描的時候,可以定位到符合前綴為 ‘j’ 的 name 字段的第一條記錄,然后沿著記錄所在的鏈表向后掃描,直到某條記錄的 name 前綴不為 ‘j’ 為止。

所以 a 字段可以在聯合索引的 B+Tree 中進行索引查詢,形成的掃描區間是['j','k')。注意, j 是閉區間。如下圖:

雖然在符合前綴為 ‘j’ 的 name 字段的二級索引記錄的范圍里,age 字段的值是「無序」的,但是對于符合 name = j 的二級索引記錄的范圍里,age字段的值是「有序」的(因為對于聯合索引,是先按照 name 字段的值排序,然后在 name 字段的值相同的情況下,再按照 age 字段的值進行排序)。

于是,在確定需要掃描的二級索引的范圍時,當二級索引記錄的 name 字段值為 ‘j’ 時,可以通過 age = 22 條件減少需要掃描的二級索引記錄范圍(age 字段可以利用聯合索引進行索引查詢的意思)。也就是說,從符合 name = 'j' and age = 22 條件的第一條記錄時開始掃描,而不需要從第一個 name 為 j 的記錄開始掃描 。如下圖的右邊:

所以,Q4 這條查詢語句 a 和 b 字段都用到了聯合索引進行索引查詢。

我們也可以在執行計劃中的 key_len 知道這一點。本次例子中:

name 字段的類型是 varchar(30) 且不為 NULL,數據庫表使用了 utf8mb4 字符集,一個字符集為 utf8mb4 的字符是 4 個字節,因此 name 字段的實際數據最多占用的存儲空間長度是 120 字節(30 x 4),然后因為 name 是變長類型的字段,需要再加 2,也就是 name 的 key_len 為 122。

age 字段的類型是 int 且不為 NULL,key_len 為 4。

Q4 查詢語句的執行計劃如下:

可以看到 key_len 為 126 字節,name 的 key_len 為 122,age 的 key_len 為 4,說明優化器使用了 2 個字段的查詢條件來形成掃描區間的邊界條件,也就是 name 和 age 字段都用到了聯合索引進行索引查詢。

通過 Q4 查詢語句我們可以知道,雖然 name 字段使用了 like 前綴匹配進行范圍查詢,但是聯合索引的最左匹配原則并沒有在遇到 name 字段的范圍查詢( like 'j%')后就停止匹配了,age 字段還是可以用到了聯合索引的。

小結

網上傳來穿去這句話:「聯合索引的最左匹配原則會一直向右匹配直到遇到范圍查詢(>、<、between、like) 就會停止匹配」并不是對的。

經過實驗的證明,我得出的結論是這樣的:

聯合索引的最左匹配原則,在遇到范圍查詢(如 >、<)的時候,就會停止匹配,也就是范圍查詢的字段可以用到聯合索引,但是在范圍查詢字段后面的字段無法用到聯合索引。注意,對于 >=、<=、BETWEEN、like 前綴匹配的范圍查詢,并不會停止匹配。
?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/161598.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/161598.shtml
英文地址,請注明出處:http://en.pswp.cn/news/161598.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【計算機網絡學習之路】TCP socket編程

文章目錄 前言一. 服務器1. 初始化服務器2. 啟動服務器 二. 客戶端三. 多進程服務器結束語 前言 本系列文章是計算機網絡學習的筆記&#xff0c;歡迎大佬們閱讀&#xff0c;糾錯&#xff0c;分享相關知識。希望可以與你共同進步。 本篇博客基于UDP socket基礎&#xff0c;介紹…

Oracle的控制文件多路復用,控制文件備份,控制文件手工恢復

一.配置控制文件多路復用 1.查詢Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

【docker】docker總結

一、Docker簡介 Docker是開源應用容器引擎&#xff0c;輕量級容器技術。基于Go語言&#xff0c;并遵循Apache2.0協議開源Docker可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中&#xff0c;然后發布到任何流行的Linux系統上&#xff0c;也可以實現虛擬化容…

No matching variant of com.android.tools.build:gradle:7.4.2 was found.

一、報錯信息 創建個新項目&#xff0c;運行直接報錯&#xff0c;信息如下&#xff1a; No matching variant of com.android.tools.build:gradle:7.4.2 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar,…

shell 條件語句

目錄 測試 test測試文件的表達式 是否成立 格式 選項 比較整數數值 格式 選項 字符串比較 常用的測試操作符 格式 邏輯測試 格式 且 &#xff08;全真才為真&#xff09; 或 &#xff08;一真即為真&#xff09; 常見條件 雙中括號 [[ expression ]] 用法 &…

springboot啟動過程

1、SpringApplication new一個對象會優先調用initialize方法 public SpringApplication(Object... sources) {initialize(sources); } private void initialize(Object[] sources) {//添加配置類SpringBootApplicationif (sources ! null && sources.length > 0) …

關于一些bug的解決1、el-input的輸入無效2、搜索之后發現數據不對3、el多選框、單選框點擊無用4、

el-input輸入無效 原來的代碼是 var test null 但是我發現不能輸入任何值 反倒修改test的初始值為123是可以的 于是我確定綁定沒問題 就是修改的問題 于是改成 var test ref&#xff08;&#xff09; v-model綁定的值改成test.value就可以了 因為ref是相應式的 可以通過輸入…

【算法】奇偶游戲(帶權并查集)

題目 小 A 和小 B 在玩一個游戲。 首先&#xff0c;小 A 寫了一個由 0 和 1 組成的序列 S&#xff0c;長度為 N。 然后&#xff0c;小 B 向小 A 提出了 M 個問題。 在每個問題中&#xff0c;小 B 指定兩個數 l 和 r&#xff0c;小 A 回答 S[l~r] 中有奇數個 1 還是偶數個 …

cocos2dx ??Animate3D(三)

一些總結 動作&#xff08;Actions&#xff09; move移動&#xff1a;moveto/moveby 從一個位置移動到另外一個位置 從一個位置移動多少數量級rotate旋轉&#xff1a;rotateto/rotateby 從一個角度旋轉到另外一個角度 旋轉多少個數量級scale縮放&#xff1a;scaleto/scaleby …

vue實現瀏覽器禁止鼠標選中文字禁止右鍵禁止F12鍵

1. 禁止鼠標選中文字 document.onselectstart new Function("event.returnValuefalse");2.禁止右鍵 document.oncontextmenu new Function("event.returnValuefalse");3. 禁止F12鍵 document.addEventListener("keydown", function (e) {if…

Go語言多線程爬蟲萬能模板它來了!

對于長期從事爬蟲行業的技術員來說&#xff0c;通過技術手段實現抓取海量數據并且做到可視化處理&#xff0c;我在想如果能寫一個萬能的爬蟲模板&#xff0c;后期遇到類似的工作只要套用模板就能解決大部分的問題&#xff0c;如此提高工作效率何樂而不為&#xff1f; 以下是一個…

有關Vue、微信小程序、UniApp中的CSS中的寬度width單位、自適應

在Vue中&#xff0c;可以使用以下單位來設置寬度&#xff08;width&#xff09; 像素&#xff08;px&#xff09;&#xff1a;最常用的單位&#xff0c;表示一個絕對長度單位。例如&#xff0c;width: 200px; 表示寬度為200像素。百分比&#xff08;%&#xff09;&#xff1a;…

Mac自帶的看圖如何連續查看多張圖片

一、問題 mac看訪達里的圖片時&#xff0c;雙擊打開一張圖片&#xff0c;然后按上下左右鍵都沒法切換到另外的圖片。而且也沒找到像window一樣單擊縮略圖可以看到預覽圖。其實是自己不懂得怎么使用&#xff0c;哈哈哈&#x1f602; 二、方法 2.1、圖標方式 可以看到縮略圖&a…

新的centos7.9安裝jenkins(二)

更多ruoyi-nbcio功能請看演示系統 gitee源代碼地址 前后端代碼&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后臺管理系統 接上一節文章。 這個版本默認git也安裝好了&#xff0c;所以全局配置這個不需要了。 maven安裝3.9.3版本…

前綴和——DP35 【模板】二維前綴和

文章目錄 &#x1f34e;1. 題目&#x1f352;2. 算法原理&#x1f345;3. 代碼實現 &#x1f34e;1. 題目 題目鏈接&#xff1a;【模板】二維前綴和_牛客題霸_牛客網 (nowcoder.com) 描述 給你一個 n 行 m 列的矩陣 A &#xff0c;下標從1開始。 接下來有 q 次查詢&#xff0…

ElasticSearch的日志配置

ElasticSearch默認情況下使用Log4j2來記錄日志&#xff0c;日志配置文件的路徑為$ES_HOME/config/log4j2.properties&#xff0c;配置方法見Log4j2的官方文檔。 參考path-settings&#xff0c;通過指定path.logs&#xff0c;可以指定日志文件的保存路徑。 在日志配置文件$ES_…

【OpenCV實現圖像:使用OpenCV生成拼圖效果】

文章目錄 概要通用配置不考慮間隔代碼實現考慮間隔代碼實現小結 概要 概要&#xff1a; 拼圖效果是一種將圖像切割為相鄰正方形并重新排列的藝術效果。在生成拼圖效果時&#xff0c;可以考慮不同的模式&#xff0c;包括是否考慮間隔和如何處理不能整除的部分。 不考慮間隔&a…

【NLP】GPT 模型如何工作

介紹 2021 年&#xff0c;我使用 GPT 模型編寫了最初的幾行代碼&#xff0c;那時我意識到文本生成已經達到了拐點。我要求 GPT-3 總結一份很長的文檔&#xff0c;并嘗試了幾次提示。我可以看到結果比以前的模型先進得多&#xff0c;這讓我對這項技術感到興奮&#xff0c;并渴望…

HQL刷題 50道

HQL刷題 50道 尚硅谷HQL刷題網站 答案 1.查詢累積銷量排名第二的商品 select sku_id from (select sku_id, dense_rank() over (order by total desc) rnfrom (select sku_id, sum(sku_num) totalfrom order_detailgroup by sku_id) t1) t2 where rn 2;2.查詢至少連續三天下…

php 時區查看和設置

php的時區&#xff0c;關系到相關時間函數的結果 其他相關&#xff1a; linux時區設置&#xff1a;鏈接 pgsql時區設置&#xff1a; 一、查看可以用的時區列表 新建一個php文件&#xff0c;輸入下面程序即可 <?php echo "<pre>"; var_dump(timezone_id…