最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL 聯(lián)合索引實戰(zhàn)示例

 更新時間:2026年04月07日 09:23:43   作者:辰風沐陽  
文章講解了MySQL聯(lián)合索引的原理、使用場景和優(yōu)化策略,通過實際項目案例分析了如何根據(jù)業(yè)務(wù)需求合理設(shè)計索引,優(yōu)化查詢性能,最后解釋了覆蓋索引的概念及其在查詢中的優(yōu)勢,強調(diào)了索引設(shè)計的重要性,感興趣的朋友跟隨小編一起看看吧

1. 聯(lián)合索引

MySQL 聯(lián)合索引(也稱復合索引)是在表的多個列上共同創(chuàng)建的一個索引,能極大的優(yōu)化多條件查詢的性能

它并非多個單列索引的簡單疊加,而是一個將多列值組合在一起,并按照特定順序進行排序和存儲的 B+ 樹結(jié)構(gòu)

假設(shè)創(chuàng)建了一個聯(lián)合索引 (A, B, C),聯(lián)合索引設(shè)計速查表:

查詢語句索引使用情況原因分析
A = 1 AND B = 2 AND C = 3全用完美匹配,效率最高
A = 1 AND B = 2A, B 有效符合最左前綴原則
A = 1A 有效符合最左前綴原則
B = 2 AND C = 3失效缺少最左列 A,索引無法使用
A = 1 AND C = 3僅 A 有效跳過了中間列 B, C 無法使用索引
A = 1 AND B > 10 AND C = 3A,B 有效B 是范圍查詢,導致 C 失效(范圍列右側(cè)失效)
A = 1 ORDER BY BA,B 有效索引可用于排序優(yōu)化

2. 最左側(cè)原則

最左側(cè)原則:在使用聯(lián)合索引時,查詢條件必須從索引的最左邊一列開始匹配,并且匹配過程不能跳過中間的列

這是理解和使用聯(lián)合索引的基石,聯(lián)合索引(a,b,c)的底層 B+ 樹是按照(a,b,c)的順序進行排序的,所以必須遵循該原則

接下來,我們通過一個案例來理解最左側(cè)原則,運行以下命令創(chuàng)建一個測試表:

CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` tinyint(4) DEFAULT NULL COMMENT '年齡',
  `gender` char(1) DEFAULT NULL COMMENT '性別',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';
INSERT INTO `users` (`name`, `age`, `gender`) VALUES ('liang', 18, '男');
INSERT INTO `users` (`name`, `age`, `gender`) VALUES ('zhang', 20, '女');
INSERT INTO `users` (`name`, `age`, `gender`) VALUES ('chen', 25, '男');
INSERT INTO `users` (`name`, `age`, `gender`) VALUES ('sun', 30, '女');

運行以下命令分析 SQL 語句,可以發(fā)現(xiàn)是全表掃描數(shù)據(jù)

explain select * from users where name = 'liang' and age = 18 and gender = '男';

我們準備使用聯(lián)合索引來優(yōu)化這個 SQL,創(chuàng)建聯(lián)合索引語法格式:

  • index_name:索引名稱,為索引指定的名稱
  • table_name:指定表名,說明是給哪張表創(chuàng)建索引
create index index_name on table_name (column1, column2, ...);

所以,我們可以運行以下命令創(chuàng)建聯(lián)合索引:

-- 相當于構(gòu)建了一個按照 name > age > gender 順序排序的數(shù)據(jù)結(jié)構(gòu)
create index idx_name_age_gender on users (name, age, gender);

聯(lián)合索引創(chuàng)建之后,那么它在什么時候有效呢 ?需要認真思考一下這個問題

有效查詢:

  • where name = 1(匹配最左列,有效)
  • where name = 1 and age = 2(匹配前兩列,有效)
  • where name = 1 and age = 2 and gender = 3(完全匹配,有效)

無效或部分失效查詢:

  • where age = 2(跳過最左列 name,完全失效)
  • where name = 1 and gender = 2(跳過中間列 age,僅 name 生效,gender 失效)
-- 聯(lián)合索引有效
explain select * from users where name = 'liang';
explain select * from users where name = 'liang' and age = 18;
explain select * from users where name = 'liang' and age = 18 and gender = '男';
-- 聯(lián)合索引無效
explain select * from users where age = 18;
-- 僅 name 生效,gender 失效
explain select * from users where name = 'liang' and gender = '男';

底層原理:為什么要遵守這個原則 ?理解這個原則的關(guān)鍵在于理解 B+ 樹的存儲結(jié)構(gòu)

聯(lián)合索引在底層并不簡單的三個字段并列,而是層級排序的。(可以把它想象為一本電話簿或字典)

  • 先 a 排序:就像電話簿先按 “姓氏” 排序
  • a 相同,再按 b 排序:姓氏相同的人,再按名字排序
  • a 和 b 都相同,再按 c 排序

為什么跳過 a 就不行 ?

  • 因為 b 列的數(shù)據(jù)在全表中并不是有序的,它只在 a 相同的小組內(nèi)是有序的
  • 如果直接查 b,數(shù)據(jù)庫就像在一本亂序的書中找字,只能全表掃描,此時聯(lián)合索引根本就不會生效

為什么跳過 bc 就不行 ?

  • 同理,c 只有在 ab 都確定的情況下才是有序的
  • 如果只給了 ac,數(shù)據(jù)庫可以使用 a 快速定位到一大塊區(qū)域,但在這塊區(qū)域里,c 是亂序的

3. 范圍查詢打斷

前面我們說的都是等值查詢,但如果是范圍查詢和模糊查詢呢 ?這兩個場景容易踩坑,也是面試和實戰(zhàn)中的高頻考點

范圍查詢打斷(范圍查詢后面的列索引失效):

  • 在聯(lián)合索引中,一旦某一列使用了范圍查詢(>、<、between),該列右側(cè)的所有列都無法再利用索引進行快速查找

為什么會這樣 ?(底層原理)

這是因為聯(lián)合索引的 B+ 樹是按照從左到右的順序構(gòu)建的:

  • a 列:全局有序
  • b 列:只有在 a 相同的情況下,才是有序的
  • c 列:只有在 a 和 b 都相同的情況下,才是有序的

當你使用 a = 1 and b > 10 時:

數(shù)據(jù)庫找到了 a = 1 的數(shù)據(jù)塊,然后在這個塊里找 b > 10 的數(shù)據(jù)。因為 b 是范圍查找,它匹配到了多個 b 的值

在這些不同的 b 值下,c 列的數(shù)據(jù)是雜亂無章的(因為 c 只有在 b 固定時才有序),既然 c 是亂序的,索引樹就無法利用二分查找來定位 c,只能遍歷掃描

實戰(zhàn)案例演示,假設(shè)聯(lián)合索引為 idx(a, b, c)

查詢語句索引使用情況解釋說明
where a = 1 and b > 10 and c > 2a,b 有效,c 失效b 使用了范圍,導致后面的 c 無法使用索引定位
where a = 1 and b = 2 and c > 5a,b,c 全有效范圍查詢在最后一列,前面都是等值,所以都能用到
where a > 1 and b = 2 and c = 3a 有效 b,c 失效a 使用了范圍,直接導致后面的 b,c 失效
where a = 1 and c > 5a 生效 c 失效雖然 c 使用了范圍,因為跳過了中間 b,所以 c 失效

注:通常我們將等值查詢的列放在前面,范圍查詢的列放在最后,這樣能最大化利用索引

4. 模糊查詢(like)

核心規(guī)則:模糊查詢是否走索引,完全取決于通配符 % 的位置

假設(shè)索引為 idx(name) 或聯(lián)合索引的最左列:

模糊查詢類型SQL 示例索引情況原理分析
前綴匹配like "abc%"生效索引樹是按照字符順序排的,
abc 開頭的字符串在樹中是連續(xù)存儲的,
數(shù)據(jù)庫可以快速定位 abc 的起始位置并掃描
后綴匹配like "%abc"失效abc 結(jié)尾的字符串在索引樹中是分散的,
無法通過索引定位,只能全表掃描
包含匹配like "%abc%"失效同上,數(shù)據(jù)在索引中無序,無法利用索引

聯(lián)合索引中的模糊查詢行為遵循 “最左前綴原則” 和 “范圍打斷原則” 的混合邏輯

場景 A:前綴匹配(視為等值)

  • where a = 1 and b like '梁%' and c = 3
  • 結(jié)果:a,b,c 全部生效
  • 原因:like '梁%' 在索引中會被視為一個確定的范圍起點,它不會打斷后續(xù)列的有序性(類似于等值查詢)

場景 B:后綴/包含匹配(視為全表掃描)

  • where a = 1 and b like '%梁' and c = 3
  • 結(jié)果:只有 a 生效,bc 失效
  • 原因:b 的查詢無法利用索引,相當于在 a = 1 的結(jié)果集中做全表掃描

場景 C:前綴匹配作為范圍(打斷后續(xù))

  • 雖然 like '梁%' 能走索引,但在某些嚴格定義下,它被視作一種范圍
  • 不過,在 MySQL 的聯(lián)合索引中,like '梁%' 不會打斷后續(xù)列(只要它是前綴匹配),MySQL 優(yōu)化器做了處理
  • 特例:如果是 like 'ab%c' (中間有通配符),則視為范圍/失效,后續(xù)列無法使用索引

為了方便記憶,可以參考這張表:

場景關(guān)鍵特征索引是否生效建議
范圍查詢>、<、between當前列生效,后續(xù)列失效將范圍查詢的列盡量放在索引的最后一列
前綴匹配LIKE 'abc%'生效盡量使用前綴匹配
后綴/包含匹配LIKE '%abc'失效如果必須后綴/包含匹配,考慮用全文索引或搜索引擎(Elasticsearch)

5. 實際項目示例

假設(shè)正在開發(fā)一個電商后臺,有一張訂單表 orders,數(shù)據(jù)量很大(百萬級)

CREATE TABLE `orders` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `order_no` varchar(64) NOT NULL COMMENT '訂單編號',
  `user_id` bigint(20) NOT NULL COMMENT '用戶ID',
  `merchant_id` bigint(20) NOT NULL COMMENT '商家ID',
  `status` varchar(20) NOT NULL COMMENT '訂單狀態(tài):UNPAID, PAID, SHIPPED, FINISHED',
  `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '訂單金額',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='電商訂單表';

創(chuàng)建一個存儲過程,用于生成測試數(shù)據(jù)

DELIMITER $$
CREATE PROCEDURE batch_insert_orders()
BEGIN
  DECLARE i INT DEFAULT 1;
  DECLARE v_user_id BIGINT;
  DECLARE v_merchant_id BIGINT;
  DECLARE v_status VARCHAR(20);
  -- 開啟事務(wù),提高插入速度
  START TRANSACTION;
  WHILE i <= 10000 DO
    -- 隨機生成用戶ID (800-999)
    SET v_user_id = FLOOR(800 + RAND() * 200);
    -- 隨機生成商家ID (1001-1005)
    SET v_merchant_id = FLOOR(1001 + RAND() * 5);
    -- 隨機狀態(tài)
    SET v_status = ELT(FLOOR(1 + RAND() * 4), 'UNPAID', 'PAID', 'SHIPPED', 'FINISHED');
    INSERT INTO `orders` (`order_no`, `user_id`, `merchant_id`, `status`, `amount`, `create_time`)
    VALUES (
      CONCAT('ORD_', DATE_FORMAT(NOW(), '%Y%m%d'), '_', LPAD(i, 6, '0')),
      v_user_id,
      v_merchant_id,
      v_status,
      ROUND(RAND() * 1000, 2),
      DATE_ADD(NOW(), INTERVAL -FLOOR(RAND() * 30) DAY) -- 隨機過去30天內(nèi)的時間
    );
    SET i = i + 1;
  END WHILE;
  COMMIT;
END$$
DELIMITER ;
-- 調(diào)用存儲過程執(zhí)行插入
CALL batch_insert_orders();

場景一:后臺訂單列表查詢(最典型)

業(yè)務(wù)需求:運營人員經(jīng)常在后臺查詢 某個商家特定日期范圍內(nèi)待發(fā)貨 訂單

-- 當前我們還沒有加索引,現(xiàn)在查詢是全表掃描
SELECT * FROM orders
	WHERE merchant_id = 1001
	AND status = 'UNPAID'
	AND create_time > '2023-10-01';

索引設(shè)計策略:我們需要建立聯(lián)合索引 (merchant_id, status, create_time)

  • merchant_id(等值查詢):這是最左列,用來先鎖定是哪個商家的數(shù)據(jù),過濾性最強
  • status(等值查詢):在商家的數(shù)據(jù)里,再篩選出特定狀態(tài)的訂單
  • create_time(范圍查詢):最后處理時間范圍。根據(jù)“最左前綴原則”和“范圍截斷規(guī)則”,必須放在聯(lián)合索引的最后面
-- 創(chuàng)建聯(lián)合索引之后,再執(zhí)行上面命令分析 SQL 語句可以看到聯(lián)合索引已被使用
create index idx_merchant_status_time on orders (merchant_id, status, create_time);

場景二:用戶訂單列表查詢(排序優(yōu)化)

業(yè)務(wù)需求:C 端用戶查看 “我的訂單”,通常按時間倒序排列

SELECT * FROM orders WHERE user_id = 888 ORDER BY create_time DESC LIMIT 20;

索引設(shè)計策略:建立聯(lián)合索引 (user_id, create_time)

  • 如果不把 create_time 加到索引里,會先查出該用戶的所有訂單,然后在內(nèi)存中進行排序(Filesort),這在數(shù)據(jù)量比較大時非常慢
  • 建立了聯(lián)合索引后,索引樹本身就是按照 user_id 分組,組內(nèi)按照 create_time 排序的。數(shù)據(jù)庫可以直接利用索引的有序性,從后往前讀取20條數(shù)據(jù),效率極高
create index idx_user_time on orders (user_id, create_time);

場景三:覆蓋索引(無需回表,極致性能)

業(yè)務(wù)需求:在訂單列表頁,只需要展示 “訂單號” 和 “當前狀態(tài)”,不需要展示收獲地址等大字段的詳情

SELECT order_no, status FROM orders WHERE user_id = 888 AND create_time > '2023-01-01';

索引設(shè)計策略:建立聯(lián)合索引 (user_id, create_time, order_no, status)

  • 這個索引包含了 where 條件用到的列,也包含了 select 查詢的列
  • MySQL 引擎發(fā)現(xiàn)索引樹上已經(jīng)有所需的所有數(shù)據(jù),完全不需要回表(不需要去查主鍵索引拿數(shù)據(jù)),直接在索引樹上遍歷返回結(jié)果。這是查詢速度的天花板
create index idx_uid_time_no_status on orders (user_id, create_time, order_no, status);

6. 覆蓋索引的理解

在場景三中使用了覆蓋索引,你可能不太理解什么是覆蓋索引,我們來研究一下這個問題

首先,需要先了解一下數(shù)據(jù)庫中的原理,在 MySQL 的 InnoDB 引擎里:

  • 主鍵索引(聚簇索引):它的葉子節(jié)點存儲了完整的行數(shù)據(jù)
  • 普通索引(二級索引):它的葉子節(jié)點只存儲了索引列的值 + 主鍵的值

執(zhí)行查詢時,如果使用二級索引,通常會發(fā)生兩件事:

  • 查二級索引:先在二級索引樹上找到符合條件的記錄,拿到主鍵 ID
  • 回表:拿著這個主鍵 ID,再去聚簇索引(主鍵索引)樹上查找完整的行數(shù)據(jù)

“回表” 是一次額外的、昂貴的 I/O 操作,而覆蓋索引的精髓就在于:

  • SQL 語句中 SELECT 的所有字段,恰好都包含在使用的這個二級索引中,無需回表拿著主鍵 ID 查詢完整行數(shù)據(jù)

結(jié)合 orders 表來理解,回到場景三,建立的索引是:(user_id, create_time, order_no, status)

SELECT order_no, status FROM orders WHERE user_id = 888 AND create_time > '2023-01-01';

為什么這個索引是覆蓋索引 ?先來拆解一下

  • where 條件字段:user_idcreate_time。這兩個字段是索引的最左兩列,數(shù)據(jù)庫會利用它們快速定位數(shù)據(jù)
  • select 查詢字段:order_nostatus。這兩個字段也包含我們建立的聯(lián)合索引中

執(zhí)行過程(使用覆蓋索引):

  • 數(shù)據(jù)庫引擎在索引樹上找到 user_id = 888 AND create_time > '2023-01-01' 的所有節(jié)點
  • 它發(fā)現(xiàn)這些索引節(jié)點上已經(jīng)直接存好了 order_nostatus 的值
  • 任務(wù)完成,直接把 order_nostatus 返回。整個過程沒有去查主鍵索引,也就沒有發(fā)生回表

對比一下,如果不用覆蓋索引會怎么樣 ?

假設(shè)我們的索引只是 (user_id, create_time),那么執(zhí)行過程需要回表:

  • 數(shù)據(jù)庫引擎在索引樹上找到 user_id = 888 AND create_time > '2023-01-01' 的所有節(jié)點
  • 但是這個索引樹上只有 user_id、create_timeid,它沒有 order_nostatus
  • 于是,數(shù)據(jù)庫必須拿著找到的 id,回到主鍵索引樹上去查找完整的行數(shù)據(jù),才能拿到 order_nostatus
  • 這個 “回到主鍵索引樹查找” 的過程,就是 “回表”

總結(jié):覆蓋索引不是一種特殊的索引類型,而是一種高效的查詢狀態(tài)

  • 核心思想:你需要的,索引里全都有
  • 最大好處:避免回表,極大的減少了磁盤的 I/O,讓查詢速度飛快
  • 如何判斷:當你用 explain 分析 SQL 時,如果 extra 列顯示為 Using index,那就說明用上了覆蓋索引

到此這篇關(guān)于MySQL 聯(lián)合索引的文章就介紹到這了,更多相關(guān)mysql 聯(lián)合索引內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • DOS命令行窗口mysql中文顯示亂碼問題解決方法

    DOS命令行窗口mysql中文顯示亂碼問題解決方法

    MySQL的默認編碼是Latin1,不支持中文,如何修改MySQL的默認編碼呢,下面為大家詳細介紹下
    2014-05-05
  • mysql如何查詢兩個日期之間最大的連續(xù)登錄天數(shù)

    mysql如何查詢兩個日期之間最大的連續(xù)登錄天數(shù)

    在現(xiàn)在的很多網(wǎng)站中都有這樣一個功能。記錄用戶的連續(xù)登陸天數(shù),所謂的連續(xù)在線是指相鄰兩天都登錄過,不一定一直在線,但是只要有過登錄即可。這篇文章主要介紹的是利用sql語句如何查詢在兩個日期之間最大的連續(xù)登錄天數(shù),有需要的朋友們下面來一起看看吧。
    2016-10-10
  • mysql 5.7.18 zip版安裝配置方法圖文教程(win7)

    mysql 5.7.18 zip版安裝配置方法圖文教程(win7)

    這篇文章主要為大家詳細介紹了win7下mysql 5.7.8 zip版安裝配置方法圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 深入理解where 1=1的用處

    深入理解where 1=1的用處

    本篇文章是對where 1=1的用處進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • MySQL數(shù)據(jù)庫字段超長問題的解決

    MySQL數(shù)據(jù)庫字段超長問題的解決

    這篇文章主要介紹了MySQL數(shù)據(jù)庫字段超長問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • mysql臨時表插入數(shù)據(jù)方式

    mysql臨時表插入數(shù)據(jù)方式

    這篇文章主要介紹了mysql臨時表插入數(shù)據(jù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 一步步帶你學習設(shè)計MySQL索引數(shù)據(jù)結(jié)構(gòu)

    一步步帶你學習設(shè)計MySQL索引數(shù)據(jù)結(jié)構(gòu)

    索引是存儲索引用于快速找到數(shù)據(jù)記錄的一種數(shù)據(jù)結(jié)構(gòu),就好比一本書的目錄部分,通過目錄中對應(yīng)的文章的頁碼,便可以快速定位到需要的文章,下面這篇文章主要給大家介紹了關(guān)于MySQL索引數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • mysql 根據(jù)時間范圍查詢數(shù)據(jù)的操作方法

    mysql 根據(jù)時間范圍查詢數(shù)據(jù)的操作方法

    這篇文章主要介紹了mysql 根據(jù)時間范圍查詢數(shù)據(jù)的操作方法,下面是一些常見的時間范圍查詢示例代碼,需要的朋友可以參考下
    2024-01-01
  • 淺談MySQL中的六種日志

    淺談MySQL中的六種日志

    MySQL中存在著6種日志,本文是對MySQL日志文件的概念及基本使用介紹,不涉及底層內(nèi)容,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Mysql獲取當前日期的前幾天日期的方法

    Mysql獲取當前日期的前幾天日期的方法

    這篇文章主要介紹了Mysql獲取當前日期的前幾天日期的方法,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下
    2015-03-03

最新評論

调兵山市| 长武县| 徐闻县| 广德县| 江达县| 白水县| 大方县| 南靖县| 彩票| 乌拉特后旗| 梅河口市| 西乌珠穆沁旗| 米易县| 宕昌县| 青龙| 邻水| 五华县| 通辽市| 交城县| 同江市| 新巴尔虎左旗| 黔江区| 项城市| 会同县| 右玉县| 克拉玛依市| 瓦房店市| 安达市| 宁晋县| 习水县| 商水县| 尤溪县| 合川市| 泗阳县| 新源县| 修武县| 贺州市| 黎平县| 长海县| 公安县| 绥化市|