MySQL組合索引使用及說明
一、索引下推
索引下推(Index Condition Pushdown,ICP) 是 MySQL 針對聯(lián)合索引的一種查詢優(yōu)化機(jī)制,核心是將部分 WHERE 條件的過濾操作下推到存儲(chǔ)引擎層,減少回表次數(shù),提升查詢效率。
1. 核心原理與對比
無 ICP 時(shí)的執(zhí)行流程存儲(chǔ)引擎通過聯(lián)合索引找到所有符合索引最左前綴條件的記錄,會(huì)先將這些記錄的主鍵值返回給 Server 層;Server 層再根據(jù)主鍵回表查詢完整數(shù)據(jù),最后執(zhí)行剩余
WHERE條件的過濾。這個(gè)過程會(huì)產(chǎn)生大量無效的回表操作。開啟 ICP 時(shí)的執(zhí)行流程存儲(chǔ)引擎在遍歷聯(lián)合索引的過程中,會(huì)同時(shí)判斷該索引包含的字段是否滿足剩余的
WHERE條件,只將滿足條件的主鍵值返回給 Server 層;Server 層再根據(jù)這些主鍵回表查詢,直接減少了回表的次數(shù)。
2. 適用場景與限制
- 適用:僅對 range、ref、eq_ref、ref_or_null 類型的索引掃描生效,且針對的是聯(lián)合索引中除最左前綴外的字段的過濾條件。
- 限制:不支持覆蓋索引(因?yàn)楦采w索引無需回表,ICP 無優(yōu)化意義);不支持子查詢條件;不支持存儲(chǔ)函數(shù)、觸發(fā)器相關(guān)的條件過濾。
3. 示例
假設(shè)有聯(lián)合索引 idx_name_age (name, age),查詢 SELECT * FROM user WHERE name LIKE '張%' AND age = 20;
- 無 ICP:存儲(chǔ)引擎先找出所有
name LIKE '張%'的索引記錄,返回主鍵給 Server 層;Server 層回表后過濾age=20。 - 有 ICP:存儲(chǔ)引擎在遍歷
name LIKE '張%'的索引時(shí),直接過濾age=20的記錄,只返回符合條件的主鍵,減少回表次數(shù)。
二、沒有索引下推功能時(shí),創(chuàng)建聯(lián)合索引的目的是什么
即使沒有 ICP 功能,創(chuàng)建聯(lián)合索引依然有核心價(jià)值,其本質(zhì)是利用索引的有序性實(shí)現(xiàn)快速檢索,減少全表掃描,核心作用體現(xiàn)在以下兩點(diǎn):
1. 滿足最左前綴匹配,實(shí)現(xiàn)高效查詢
聯(lián)合索引遵循最左前綴原則,只要查詢條件包含索引的最左字段,就能觸發(fā)索引掃描,大幅減少需要遍歷的數(shù)據(jù)量。
比如聯(lián)合索引 idx_name_age (name, age),查詢 WHERE name = '張三' 或 name LIKE '張%' 時(shí),存儲(chǔ)引擎會(huì)直接通過索引定位到符合 name 條件的記錄區(qū)間,避免全表掃描,這是聯(lián)合索引的核心優(yōu)勢,和 ICP 無關(guān)。
2. 覆蓋索引場景下,完全避免回表
若查詢的字段全部包含在聯(lián)合索引中(覆蓋索引),無論是否開啟 ICP,存儲(chǔ)引擎都能直接從索引中獲取數(shù)據(jù),無需回表查詢主鍵對應(yīng)的完整行。
比如 SELECT name, age FROM user WHERE name = '張三',聯(lián)合索引 idx_name_age 已經(jīng)包含查詢所需的所有字段,此時(shí)查詢效率極高,ICP 在此場景下無優(yōu)化意義。
簡單來說:ICP 是聯(lián)合索引的 "增值優(yōu)化",用于減少回表次數(shù);而聯(lián)合索引的核心價(jià)值是基于最左前綴的快速檢索 + 覆蓋索引的無回表查詢,這兩個(gè)核心作用不依賴 ICP 就能生效。
三、無索引覆蓋和索引下推時(shí),組合索引和單列索引的區(qū)別
即使沒有覆蓋索引和索引下推(ICP),聯(lián)合索引和單列索引也有本質(zhì)區(qū)別,核心差異在于索引的有序性覆蓋范圍和過濾效率,具體體現(xiàn)在兩點(diǎn):
1. 遵循最左前綴原則,支持多字段的逐層過濾
聯(lián)合索引(如 idx_name_age (name, age))是按照字段順序構(gòu)建的有序結(jié)構(gòu),查詢時(shí)可以先通過 name 篩選出一個(gè)小范圍,再在這個(gè)范圍內(nèi)基于 age 進(jìn)一步篩選,比單獨(dú)用 name 列的單列索引過濾后,在 Server 層遍歷 age 條件的效率更高。
而單列索引(如 idx_name)只能篩選 name 條件,后續(xù)的 age 過濾完全依賴 Server 層回表后處理,中間會(huì)產(chǎn)生更多的無效數(shù)據(jù)傳輸。
2. 減少索引數(shù)量,降低維護(hù)成本
一個(gè)聯(lián)合索引可以覆蓋多個(gè)前綴字段的查詢場景(如查 name、查 name+age),而如果用單列索引,需要為每個(gè)字段創(chuàng)建獨(dú)立索引,會(huì)增加索引的存儲(chǔ)開銷和增刪改操作的索引維護(hù)成本,還可能引發(fā)索引選擇的沖突。
簡單總結(jié):聯(lián)合索引的核心價(jià)值是多字段的層級化有序過濾,覆蓋索引和 ICP 只是在此基礎(chǔ)上的優(yōu)化手段,沒有它們,聯(lián)合索引依然比單列索引在多條件查詢時(shí)更高效。
四、組合索引中,最左匹配多字段逐層過濾和索引下推的區(qū)別
最左匹配多字段逐層過濾是聯(lián)合索引的核心檢索規(guī)則,索引下推(ICP)是基于聯(lián)合索引的查詢優(yōu)化手段,二者的設(shè)計(jì)目標(biāo)、生效階段、作用范圍完全不同,具體區(qū)別如下:
1. 核心目標(biāo)不同
- 最左匹配:決定聯(lián)合索引能否被觸發(fā),以及能用到索引的哪些字段。它要求查詢條件必須包含聯(lián)合索引的最左前綴字段,存儲(chǔ)引擎會(huì)先通過最左字段快速定位數(shù)據(jù)區(qū)間,再依次用后續(xù)字段縮小范圍,本質(zhì)是利用索引的有序性實(shí)現(xiàn)快速檢索。
- 索引下推:減少回表次數(shù)。它是在最左匹配觸發(fā)索引掃描后,讓存儲(chǔ)引擎在遍歷索引的階段,就過濾掉不符合非最左前綴字段條件的數(shù)據(jù),只把符合條件的主鍵返回給 Server 層,本質(zhì)是在引擎層提前過濾,減少無效回表。
2. 生效階段與范圍不同
- 最左匹配:生效在索引掃描的初始階段,作用于聯(lián)合索引的前綴字段(如
idx_name_age中的name)。如果查詢條件沒有最左前綴(比如只查age=20),聯(lián)合索引會(huì)完全失效,退化為全表掃描。 - 索引下推:生效在索引掃描的過程階段,作用于聯(lián)合索引的非前綴字段(如
idx_name_age中的age)。它必須依賴最左匹配先觸發(fā)索引掃描,否則無法生效。
3. 無 ICP 時(shí)的差異體現(xiàn)
以聯(lián)合索引 idx_name_age(name, age) 查詢 WHERE name LIKE '張%' AND age=20 為例:
- 僅最左匹配生效(無 ICP):存儲(chǔ)引擎會(huì)先通過
name LIKE '張%'找到所有符合條件的索引記錄,不管age條件,直接返回所有主鍵給 Server 層,Server 層再回表取數(shù)后過濾age=20。 - 最左匹配 + ICP 生效:存儲(chǔ)引擎通過
name LIKE '張%'找到索引記錄后,在引擎層直接過濾age=20,只返回符合條件的主鍵,Server 層回表數(shù)量大幅減少。
簡單總結(jié):最左匹配是聯(lián)合索引能用的前提,ICP 是聯(lián)合索引用得更高效的優(yōu)化。
五、舉例說明
我們以 MySQL 表 user 為例,創(chuàng)建表結(jié)構(gòu)和聯(lián)合索引,通過相同查詢語句在 無 ICP 和 有 ICP 兩種場景下的執(zhí)行流程,對比最左匹配逐層過濾和索引下推的差異。
1. 準(zhǔn)備測試環(huán)境
-- 創(chuàng)建用戶表 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(20) NOT NULL, `age` INT NOT NULL, `address` VARCHAR(50) ); -- 創(chuàng)建聯(lián)合索引 idx_name_age (最左字段 name,其次 age) CREATE INDEX idx_name_age ON `user` (`name`, `age`); -- 插入測試數(shù)據(jù) INSERT INTO `user` VALUES (1, '張三', 20, '北京'), (2, '張三', 25, '上海'), (3, '張四', 20, '廣州'), (4, '李五', 22, '深圳');
2. 測試查詢語句
執(zhí)行查詢:SELECT * FROM user WHERE name LIKE '張%' AND age = 20;這個(gè)查詢的條件滿足最左匹配(用到了索引最左字段 name),我們分別看 無 ICP 和 有 ICP 的執(zhí)行流程。
3. 場景 1:關(guān)閉 ICP,僅最左匹配生效
MySQL 中可以通過 SET optimizer_switch='index_condition_pushdown=off'; 關(guān)閉 ICP。執(zhí)行流程:
- 存儲(chǔ)引擎根據(jù)最左匹配規(guī)則,掃描聯(lián)合索引
idx_name_age,找出所有name LIKE '張%'的索引記錄,得到 3 條索引數(shù)據(jù):(張三,20,1)、(張三,25,2)、(張四,20,3)。 - 存儲(chǔ)引擎不會(huì)判斷
age=20的條件,直接把這 3 條記錄的主鍵1,2,3返回給 Server 層。 - Server 層拿著這 3 個(gè)主鍵回表 3 次,查詢出完整的用戶數(shù)據(jù),再過濾出
age=20的記錄(最終保留 id=1、3 的兩條數(shù)據(jù))。
4. 場景 2:開啟 ICP,最左匹配 + 索引下推 共同生效
通過 SET optimizer_switch='index_condition_pushdown=on'; 開啟 ICP(MySQL 5.6+ 默認(rèn)開啟)。執(zhí)行流程:
- 存儲(chǔ)引擎先通過最左匹配找到所有
name LIKE '張%'的索引記錄,得到 3 條索引數(shù)據(jù)。 - 存儲(chǔ)引擎在遍歷索引的階段,直接執(zhí)行索引下推,過濾出符合
age=20的索引記錄,僅保留(張三,20,1)、(張四,20,3)兩條。 - 存儲(chǔ)引擎把這 2 個(gè)主鍵
1,3返回給 Server 層,Server 層只需要回表 2 次,直接得到最終結(jié)果。
5. 核心差異總結(jié)
| 場景 | 最左匹配作用 | 索引下推作用 | 回表次數(shù) |
|---|---|---|---|
| 無 ICP(僅最左匹配) | 篩選出 name 符合條件的索引 | 無作用,不參與 age 過濾 | 3 次 |
| 有 ICP(兩者結(jié)合) | 篩選出 name 符合條件的索引 | 引擎層提前過濾 age 符合條件的索引 | 2 次 |
問題擴(kuò)展
疑問:不對啊,場景1中的組合索引兩個(gè)字段都會(huì)生效啊,應(yīng)該是逐層過濾,先通過name過濾再通過age過濾啊,返回給server層的應(yīng)該也是兩條數(shù)據(jù)才對啊
核心誤區(qū)在于:只有等值匹配的聯(lián)合索引才能實(shí)現(xiàn)多字段逐層過濾,LIKE '張%' 這種范圍匹配會(huì)截?cái)嗦?lián)合索引的后續(xù)字段生效。
結(jié)合之前的測試場景詳細(xì)拆解:
1. 聯(lián)合索引的逐層過濾有前提
- 聯(lián)合索引
idx_name_age(name,age)的逐層過濾,要求前面的字段是等值匹配,后續(xù)字段才能繼續(xù)參與索引過濾。 - 比如查詢
WHERE name='張三' AND age=20,存儲(chǔ)引擎會(huì)先定位name='張三'的索引區(qū)間,再在這個(gè)區(qū)間內(nèi)通過age=20進(jìn)一步過濾,最終直接返回符合條件的主鍵,這才是完整的逐層過濾。
2. 范圍匹配截?cái)嗔薬ge字段的索引過濾(無 ICP 時(shí))
- 你的查詢條件是
name LIKE '張%' AND age=20,LIKE '張%'屬于范圍匹配。 - 在無 ICP 的情況下,MySQL 的聯(lián)合索引規(guī)則是:范圍匹配后的字段,無法再通過索引逐層過濾。
- 所以存儲(chǔ)引擎只能通過
name LIKE '張%'篩選出所有前綴符合的索引記錄(3 條),age=20的條件無法在引擎層通過索引過濾,只能將這 3 條記錄的主鍵返回給 Server 層,由 Server 層回表后再過濾。
3. ICP 的核心作用就是打破這個(gè)限制
- 開啟 ICP 后,存儲(chǔ)引擎在拿到
name LIKE '張%'的 3 條索引記錄時(shí),會(huì)直接利用索引中存儲(chǔ)的age值進(jìn)行過濾,無需等到 Server 層回表,這才實(shí)現(xiàn)了age字段的引擎層過濾,最終只返回 2 條主鍵。
簡單總結(jié):等值匹配的聯(lián)合索引能逐層過濾,范圍匹配會(huì)截?cái)嗪罄m(xù)字段的索引過濾;ICP 則是讓范圍匹配后的字段,也能在引擎層基于索引數(shù)據(jù)過濾。
六、聯(lián)合索引字段生效規(guī)則速查表
以聯(lián)合索引 idx_a_b_c(字段順序:a→b→c)為基準(zhǔn),覆蓋核心生效場景、失效場景及關(guān)鍵補(bǔ)充說明,兼顧實(shí)用性與落地性。
1. 核心生效場景(遵循最左前綴法則)
| 場景類型 | 示例 SQL(where 條件) | 生效字段 | 核心邏輯 |
|---|---|---|---|
| 1. 全值等值匹配 | where a=10 and b=20 and c=30 | a、b、c(全生效) | 匹配索引完整字段,引擎逐層精準(zhǔn)過濾,效率最優(yōu)(相當(dāng)于使用 (a,b,c) 索引)。 |
| 2. 最左前綴匹配 | where a=10 | a(單獨(dú)生效) | 僅使用索引最左字段,符合 “從左開始” 原則(相當(dāng)于使用 (a) 索引)。 |
| 3. 前 N 字段等值匹配 | where a=10 and b=20 | a、b(部分生效) | 匹配索引前 2 個(gè)字段,后續(xù)字段未使用(相當(dāng)于使用 (a,b) 索引)。 |
| 4. 前 N 字段等值 + 最后字段范圍 | where a=10 and b=20 and c>30 | a、b、c(全生效) | 等值字段在前,范圍字段在最后,不截?cái)嗪罄m(xù)索引,全字段生效。 |
| 5. 最左字段前綴匹配(模糊查詢) | where a like '10%' | a(單獨(dú)生效) | 最左字段尾部模糊匹配(% 在右側(cè)),僅 a 字段索引生效;若開啟 ICP,可結(jié)合后續(xù)字段過濾。 |
| 6. 條件順序無關(guān)(優(yōu)化器自動(dòng)調(diào)整) | where b=20 and a=10 and c=30 | a、b、c(全生效) | SQL 條件編寫順序不影響,優(yōu)化器會(huì)按索引字段順序重排,滿足最左前綴即可生效。 |
| 7. 覆蓋索引查詢(無需回表) | select a,b,c from t where a=10 | a、b、c(全生效) | 查詢字段均在索引中,引擎直接從索引取數(shù),無需回表查主表數(shù)據(jù)。 |
2. 索引失效 / 部分失效場景
| 場景類型 | 示例 SQL(where 條件) | 生效字段 | 失效原因 |
|---|---|---|---|
| 1. 跳過最左字段 | where b=20 and c=30 | 無(全失效) | 違反最左前綴法則,未從索引首字段開始查詢,索引完全無法使用。 |
| 2. 跳過中間字段 | where a=10 and c=30 | a(僅左生效) | 跳過中間字段 b,后續(xù)字段 c 索引失效,僅最左字段 a 生效。 |
| 3. 中間字段范圍查詢 | where a=10 and b>20 and c=30 | a、b(部分生效) | 范圍查詢(>、<)在中間字段 b,截?cái)嗪罄m(xù)字段 c 索引,僅 a、b 生效(>=/<= 無此問題)。 |
| 4. 最左字段頭部 / 全模糊匹配 | where a like '%10' 或 a like '%10%' | 無(全失效) | % 在最左字段左側(cè)或前后都有,無法匹配索引有序結(jié)構(gòu),觸發(fā)全表掃描。 |
| 5. 索引字段運(yùn)算操作 | where substring (a,1,2)=10 或 a+1=11 | 無(全失效) | 索引字段直接做函數(shù)運(yùn)算 / 算術(shù)運(yùn)算,引擎無法識別索引結(jié)構(gòu),索引失效。 |
| 6. 字符串字段無引號 | where a=123(a 為 varchar 類型) | 無(全失效) | 類型隱式轉(zhuǎn)換,等價(jià)于對 a 做函數(shù)運(yùn)算,破壞索引匹配邏輯。 |
| 7. OR 連接條件(單側(cè)無索引) | where a=10 or d=40(d 無索引) | 無(全失效) | OR 兩側(cè)字段需均有索引,否則有索引的 a 字段也失效,觸發(fā)全表掃描。 |
| 8. 數(shù)據(jù)分布導(dǎo)致優(yōu)化器放棄 | where a is null(a 字段 90% 為 null) | 無(全失效) | 優(yōu)化器評估 “走索引比全表慢”,直接放棄索引(如查詢結(jié)果占比超 30% 常觸發(fā))。 |
3. 關(guān)鍵補(bǔ)充說明(避坑重點(diǎn))
- ICP 對生效的輔助作用:當(dāng)最左字段范圍匹配(如
a like '10%')時(shí),無 ICP 則后續(xù)字段無法索引過濾;開啟 ICP 后,引擎可直接用索引中后續(xù)字段過濾,減少回表次數(shù)(不改變 “最左字段先生效” 規(guī)則,僅優(yōu)化過濾時(shí)機(jī))。 - InnoDB 聚簇索引特性:聯(lián)合索引葉子節(jié)點(diǎn)包含主鍵,若查詢字段為 “索引字段 + 主鍵”(如
select a,b,id from t where a=10),可觸發(fā)覆蓋索引,無需回表。 - 范圍查詢細(xì)節(jié):
>/<會(huì)截?cái)嗪罄m(xù)索引,>=/<=不會(huì),優(yōu)先使用后者可提升索引利用率;between and等價(jià)于>=/<=,全字段生效。 - 生效驗(yàn)證方法:用
EXPLAIN分析 SQL,key列顯示聯(lián)合索引名則說明索引生效,key_len長度可判斷生效字段數(shù)量(長度越長,生效字段越多)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
史上最簡單的MySQL數(shù)據(jù)備份與還原教程(中)(三十六)
這篇文章主要為大家詳細(xì)介紹了史上最簡單的MySQL數(shù)據(jù)備份與還原教程中篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
MySQL MHA 運(yùn)行狀態(tài)監(jiān)控介紹
這篇文章主要介紹MySQL MHA 運(yùn)行狀態(tài)監(jiān)控,MHA(Master HA)是一款開源的 MySQL 的高可用程序,它為 MySQL 主從復(fù)制架構(gòu)提供了 automating master failover 功能,想具體了解的小伙伴可以和小編一起學(xué)習(xí)下面文章內(nèi)容2021-10-10
MySQL索引類型總結(jié)和使用技巧以及注意事項(xiàng)
索引是快速搜索的關(guān)鍵。MySQL索引的建立對于MySQL的高效運(yùn)行是很重要的。下面介紹幾種常見的MySQL索引類型2014-04-04
MySQL查看與修改當(dāng)前數(shù)據(jù)庫編碼的方法
這篇文章主要介紹了MySQL查看與修改當(dāng)前數(shù)據(jù)庫編碼的方法,需要的朋友可以參考下2016-04-04
SQL實(shí)現(xiàn)LeetCode(196.刪除重復(fù)郵箱)
這篇文章主要介紹了SQL實(shí)現(xiàn)LeetCode(196.刪除重復(fù)郵箱),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
MySQL中刪除重復(fù)數(shù)據(jù)SQL的三種寫法
這篇文章主要介紹了MySQL中刪除重復(fù)數(shù)據(jù)SQL的三種寫法,文中通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2025-01-01
MySQL數(shù)據(jù)庫安裝和Navicat for MySQL配合使用教程
MySQL是一個(gè)關(guān)系型數(shù)據(jù)庫管理系統(tǒng),由瑞典MySQL AB 公司開發(fā),目前屬于 Oracle 旗下公司。這篇文章主要介紹了MySQL數(shù)據(jù)庫安裝和Navicat for MySQL配合使用,需要的朋友可以參考下2019-06-06
將MySQL數(shù)據(jù)導(dǎo)出為sql文件的最佳實(shí)踐
這篇文章主要給大家介紹了如何將MySQL數(shù)據(jù)導(dǎo)出為sql文件,避免數(shù)據(jù)丟失的最佳實(shí)踐,文中有詳細(xì)的導(dǎo)出流程步驟,跟著文中的步驟就可以導(dǎo)出文件,需要的朋友可以借鑒參考2023-07-07

