MySQL 聚簇索引、非聚簇索引與回表詳解
引言
做后端開發(fā)的同學(xué),大概率都聽過“索引優(yōu)化”,也用過主鍵索引提升查詢速度。但你真的懂索引嗎?日常開發(fā)中,不少同學(xué)遇到查詢卡頓就盲目加索引,結(jié)果反而導(dǎo)致數(shù)據(jù)增刪改效率下降;還有人疑惑,為什么同樣是索引,主鍵查詢秒出結(jié)果,普通索引查詢卻要慢半拍?除了主鍵,還有哪些核心索引類型?為什么有的查詢不用“繞路”,有的卻要額外“回表”?今天咱們從數(shù)據(jù)庫物理存儲(chǔ)的底層邏輯出發(fā),逐字拆解聚簇索引與非聚簇索引的核心概念,幫你夯實(shí)索引入門基礎(chǔ),為后續(xù)吃透B+樹結(jié)構(gòu)、搞定索引優(yōu)化鋪路~ 建議點(diǎn)贊收藏,避免后續(xù)需要時(shí)找不到!
一、索引基礎(chǔ):不止是“快速查找”
提到索引,很多人第一反應(yīng)是“字典的目錄”——通過目錄快速定位到目標(biāo)內(nèi)容,不用逐頁翻閱。這個(gè)類比確實(shí)直觀,但不夠全面,尤其是在數(shù)據(jù)庫的物理存儲(chǔ)層面,索引的作用和底層邏輯要復(fù)雜得多。字典的目錄是靜態(tài)的,而數(shù)據(jù)庫索引是動(dòng)態(tài)維護(hù)的,數(shù)據(jù)的增刪改操作都會(huì)同步更新索引結(jié)構(gòu),這也是索引“用空間換時(shí)間”的核心代價(jià)。
核心定義:索引是數(shù)據(jù)庫中為了加速對表中數(shù)據(jù)的查找和訪問,而專門創(chuàng)建的一種有序數(shù)據(jù)結(jié)構(gòu)。它的本質(zhì)是“用空間換時(shí)間”,通過提前維護(hù)一份按特定規(guī)則排序的索引數(shù)據(jù),替代全表掃描,從而大幅減少查詢時(shí)的磁盤I/O次數(shù)(磁盤I/O是數(shù)據(jù)庫查詢的核心性能瓶頸)。
我們平時(shí)在創(chuàng)建表時(shí)定義的主鍵(PRIMARY KEY),其實(shí)就是一種特殊的索引——主鍵索引。它不僅能保證數(shù)據(jù)的唯一性和非空性,在主流數(shù)據(jù)庫(如MySQL InnoDB、Oracle)中,還會(huì)默認(rèn)基于主鍵創(chuàng)建聚簇索引(不同數(shù)據(jù)庫的索引實(shí)現(xiàn)有差異,本文以日常開發(fā)最常用的MySQL InnoDB引擎為例)。但索引的世界里,不止主鍵這一種玩家,聚簇索引和非聚簇索引,才是理解索引物理存儲(chǔ)本質(zhì)、搞定后續(xù)優(yōu)化的關(guān)鍵核心。
1.1 為什么需要關(guān)注物理存儲(chǔ)?
很多新手在索引使用上踩坑的核心原因,就是只知道“加索引能提速”,卻不懂索引在磁盤上是怎么存儲(chǔ)的,也不清楚索引查詢的底層流程。比如:
- 為什么同樣是等值查詢,主鍵查詢比普通索引查詢快好幾倍?
- 什么是“回表”?為什么回表會(huì)明顯影響查詢效率?
- 聚簇索引和非聚簇索引的存儲(chǔ)結(jié)構(gòu)有啥本質(zhì)區(qū)別?
- 為什么一張表只能有一個(gè)聚簇索引,卻能創(chuàng)建多個(gè)非聚簇索引?
只有搞懂索引的物理存儲(chǔ)邏輯,才能從根源上理解這些問題,后續(xù)做索引設(shè)計(jì)和優(yōu)化時(shí)也能有的放矢,避免盲目加索引、錯(cuò)加索引的情況~ ??
二、聚簇索引:數(shù)據(jù)與索引“合二為一”
2.1 聚簇索引的核心定義
聚簇索引(Clustered Index),也叫聚集索引,其核心特點(diǎn)是:索引的葉子節(jié)點(diǎn),就是數(shù)據(jù)本身。也就是說,聚簇索引的結(jié)構(gòu)不僅包含索引鍵值(比如主鍵id),還直接存儲(chǔ)了對應(yīng)行的完整數(shù)據(jù)記錄,索引和數(shù)據(jù)是緊密綁定、合二為一的。
用一個(gè)更形象的比喻:聚簇索引就像一本“按章節(jié)排序的教材”,章節(jié)標(biāo)題(對應(yīng)索引鍵值,比如主鍵id)和章節(jié)內(nèi)容(對應(yīng)數(shù)據(jù)行的完整信息)是綁定在一起的,找到章節(jié)標(biāo)題的位置,就能直接看到章節(jié)里的所有內(nèi)容,不用再翻到其他頁面查找。而且教材的內(nèi)容是按照章節(jié)順序排列的,這和聚簇索引決定數(shù)據(jù)物理存儲(chǔ)順序的特性完全一致。
2.2 聚簇索引的物理存儲(chǔ)邏輯(以InnoDB為例)
在MySQL InnoDB引擎中,聚簇索引的創(chuàng)建有明確的優(yōu)先級(jí)規(guī)則,開發(fā)者無需手動(dòng)指定聚簇索引類型,數(shù)據(jù)庫會(huì)自動(dòng)按照以下規(guī)則生成:
- 如果表定義了主鍵(PRIMARY KEY),那么主鍵就是聚簇索引,索引鍵值就是主鍵字段的值;
- 如果表沒有定義主鍵,數(shù)據(jù)庫會(huì)從表中選擇第一個(gè)非空的唯一索引(UNIQUE NOT NULL)作為聚簇索引;
- 如果表既沒有主鍵,也沒有合適的唯一索引,InnoDB會(huì)自動(dòng)創(chuàng)建一個(gè)隱藏的聚簇索引(名稱為row_id),該索引的鍵值是數(shù)據(jù)庫自動(dòng)生成的自增ID,用戶無法直接訪問。
聚簇索引的存儲(chǔ)結(jié)構(gòu)(簡化版,便于理解):
- 非葉子節(jié)點(diǎn):僅存儲(chǔ)索引鍵值(比如主鍵id)和指向葉子節(jié)點(diǎn)的指針,不存儲(chǔ)任何數(shù)據(jù)記錄,作用是快速定位葉子節(jié)點(diǎn)的位置;
- 葉子節(jié)點(diǎn):存儲(chǔ)完整的數(shù)據(jù)行(包含當(dāng)前表的所有字段,比如id、name、age、email等),且葉子節(jié)點(diǎn)之間會(huì)通過指針串聯(lián),形成有序的鏈表結(jié)構(gòu),便于范圍查詢。
-- 示例表:用戶表(id為主鍵,默認(rèn)創(chuàng)建聚簇索引)
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT, -- 聚簇索引鍵(主鍵)
name VARCHAR(50) NOT NULL, -- 普通字段
age INT, -- 普通字段
email VARCHAR(100) UNIQUE -- 唯一索引字段(非聚簇索引)
);
當(dāng)我們執(zhí)行SELECT * FROM user WHERE id = 100這樣的主鍵查詢時(shí),底層執(zhí)行流程非常簡潔,無需額外操作:
- 數(shù)據(jù)庫查詢優(yōu)化器會(huì)優(yōu)先選擇聚簇索引,從聚簇索引的根節(jié)點(diǎn)開始遍歷,通過二分查找快速定位到id=100對應(yīng)的葉子節(jié)點(diǎn);
- 直接從該葉子節(jié)點(diǎn)中讀取完整的用戶數(shù)據(jù)記錄(包含name、age、email等所有字段);
- 將查詢結(jié)果返回給用戶,整個(gè)過程只需要一次磁盤I/O操作(理想情況下)。
這也是為什么主鍵查詢速度最快——因?yàn)樗恍枰?ldquo;回表”,一步到位就能獲取完整數(shù)據(jù),磁盤I/O次數(shù)最少,而磁盤I/O是數(shù)據(jù)庫查詢性能的核心瓶頸。
三、非聚簇索引:索引與數(shù)據(jù)“分開存放”
3.1 非聚簇索引的核心定義
非聚簇索引(Non-Clustered Index),也叫非聚集索引,其核心特點(diǎn)與聚簇索引完全相反:索引的葉子節(jié)點(diǎn),存儲(chǔ)的不是完整數(shù)據(jù)記錄,而是對應(yīng)的聚簇索引鍵值。
還是用教材的比喻來理解:非聚簇索引就像教材末尾的“關(guān)鍵詞索引表”,表中只記錄關(guān)鍵詞(對應(yīng)非聚簇索引的鍵值,比如name字段的值)和該關(guān)鍵詞所在的章節(jié)號(hào)(對應(yīng)聚簇索引的鍵值,比如主鍵id),想要查看關(guān)鍵詞對應(yīng)的完整內(nèi)容,必須先根據(jù)章節(jié)號(hào)找到對應(yīng)的章節(jié)(對應(yīng)聚簇索引),再從章節(jié)中讀取內(nèi)容。而且關(guān)鍵詞索引表的順序和教材章節(jié)順序不一定一致,這和非聚簇索引鍵值順序與數(shù)據(jù)物理存儲(chǔ)順序無關(guān)的特性相符。
3.2 非聚簇索引的物理存儲(chǔ)與查詢流程
在InnoDB引擎中,除了聚簇索引之外的所有索引,都屬于非聚簇索引,比如普通索引(INDEX)、唯一索引(UNIQUE)、聯(lián)合索引等,這些索引的存儲(chǔ)邏輯和查詢流程完全一致。
非聚簇索引的存儲(chǔ)結(jié)構(gòu)(簡化版):
- 非葉子節(jié)點(diǎn):存儲(chǔ)非聚簇索引的鍵值(比如name字段的值)和指向葉子節(jié)點(diǎn)的指針,作用是快速定位葉子節(jié)點(diǎn);
- 葉子節(jié)點(diǎn):存儲(chǔ)對應(yīng)的聚簇索引鍵值(比如主鍵id),而非完整的數(shù)據(jù)行,葉子節(jié)點(diǎn)之間同樣會(huì)通過指針串聯(lián),保證索引鍵值的有序性。
我們給user表的name字段創(chuàng)建一個(gè)普通索引(非聚簇索引),用于優(yōu)化name字段的查詢效率:
-- 給name字段創(chuàng)建非聚簇索引(普通索引) CREATE INDEX idx_user_name ON user(name);
當(dāng)我們執(zhí)行SELECT * FROM user WHERE name = '張三'這樣的普通索引查詢時(shí),底層執(zhí)行流程會(huì)比聚簇索引查詢多一步關(guān)鍵操作——回表:
- 數(shù)據(jù)庫查詢優(yōu)化器會(huì)選擇idx_user_name非聚簇索引,從索引的根節(jié)點(diǎn)開始遍歷,通過二分查找定位到name='張三’對應(yīng)的葉子節(jié)點(diǎn);
- 從該葉子節(jié)點(diǎn)中讀取到對應(yīng)的聚簇索引鍵值(比如id=100),這一步只能獲取到主鍵id,無法獲取其他字段的數(shù)據(jù);
- 拿著獲取到的聚簇索引鍵值(id=100),去聚簇索引中再次查找對應(yīng)的葉子節(jié)點(diǎn)(這一步就是“回表”操作);
- 從聚簇索引的葉子節(jié)點(diǎn)中讀取完整的用戶數(shù)據(jù)記錄(包含name、age、email等所有字段);
- 將查詢結(jié)果返回給用戶,整個(gè)過程需要兩次磁盤I/O操作(理想情況下)。
重點(diǎn)提示:
回表:就是通過非聚簇索引找到對應(yīng)的聚簇索引鍵值后,必須再次去聚簇索引中查詢完整數(shù)據(jù)記錄的過程?;乇聿僮鲿?huì)額外增加一次磁盤I/O,而磁盤I/O是數(shù)據(jù)庫查詢的性能瓶頸,所以非聚簇索引查詢速度通常比聚簇索引查詢慢,數(shù)據(jù)量越大,這個(gè)性能差異越明顯。
四、聚簇索引與非聚簇索引核心區(qū)別(總結(jié))
為了方便大家對比記憶,清晰區(qū)分兩種索引的核心差異,這里整理了一張?jiān)敿?xì)的對比表,涵蓋日常開發(fā)中最關(guān)注的多個(gè)維度:
| 對比維度 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 存儲(chǔ)結(jié)構(gòu) | 葉子節(jié)點(diǎn)存儲(chǔ)完整數(shù)據(jù)行 | 葉子節(jié)點(diǎn)存儲(chǔ)聚簇索引鍵值 |
| 與數(shù)據(jù)的關(guān)系 | 索引即數(shù)據(jù),合二為一;索引順序決定數(shù)據(jù)物理存儲(chǔ)順序 | 索引與數(shù)據(jù)分開存放;索引順序與數(shù)據(jù)物理存儲(chǔ)順序無關(guān) |
| 查詢效率 | 高,無需回表,一次磁盤I/O(理想情況) | 較低,需回表(覆蓋索引除外),兩次磁盤I/O(理想情況) |
| 數(shù)量限制 | 一張表只能有一個(gè)(InnoDB引擎) | 一張表可以有多個(gè),無明確數(shù)量上限(受限于磁盤空間) |
| 創(chuàng)建規(guī)則(InnoDB) | 主鍵默認(rèn)創(chuàng)建,無主鍵則選唯一非空索引,均無則自動(dòng)生成隱藏索引 | 普通索引、唯一索引、聯(lián)合索引等,均為非聚簇索引,需手動(dòng)創(chuàng)建(除主鍵關(guān)聯(lián)的唯一索引外) |
| 適用場景 | 主鍵查詢、范圍查詢(數(shù)據(jù)有序,效率高) | 普通字段查詢、多條件查詢(需配合聯(lián)合索引、覆蓋索引優(yōu)化) |
這里補(bǔ)充一個(gè)日常開發(fā)中高頻用到的優(yōu)化知識(shí)點(diǎn):覆蓋索引。如果查詢的字段剛好全部包含在非聚簇索引的葉子節(jié)點(diǎn)中(比如查詢id和name字段,而idx_user_name索引的葉子節(jié)點(diǎn)包含name和對應(yīng)的id),那么就不需要執(zhí)行回表操作,這種情況就是覆蓋索引。覆蓋索引能減少一次磁盤I/O,查詢效率會(huì)大幅提升,甚至接近聚簇索引的查詢速度。
示例(覆蓋索引查詢,無需回表):
-- 只查詢name和id,兩個(gè)字段均在非聚簇索引葉子節(jié)點(diǎn)中,無需回表 SELECT id, name FROM user WHERE name = '張三';
反例(非覆蓋索引查詢,需要回表):
-- 查詢name、id和age,age字段不在非聚簇索引中,需要回表 SELECT id, name, age FROM user WHERE name = '張三';
五、結(jié)尾總結(jié)
本文我們從數(shù)據(jù)庫物理存儲(chǔ)的底層邏輯出發(fā),系統(tǒng)拆解了聚簇索引與非聚簇索引的核心概念、存儲(chǔ)結(jié)構(gòu)、查詢流程,以及“回表”操作的本質(zhì),核心要點(diǎn)總結(jié)如下:
- 聚簇索引是“索引+數(shù)據(jù)”一體化結(jié)構(gòu),主鍵默認(rèn)作為聚簇索引,查詢時(shí)無需回表,一次I/O即可獲取完整數(shù)據(jù),效率最高;
- 非聚簇索引是“索引+聚簇鍵”分離結(jié)構(gòu),除聚簇索引外的所有索引均為非聚簇索引,查詢時(shí)需通過聚簇鍵回表獲取完整數(shù)據(jù),效率較低;
- 回表是影響非聚簇索引查詢效率的核心因素,通過設(shè)計(jì)覆蓋索引(查詢字段均在索引中)可避免回表,大幅提升查詢性能;
- 聚簇索引一張表只能有一個(gè),非聚簇索引可創(chuàng)建多個(gè),日常開發(fā)需根據(jù)查詢場景合理選擇索引類型。
這些基礎(chǔ)概念是后續(xù)理解B+樹索引結(jié)構(gòu)、索引優(yōu)化(比如聯(lián)合索引設(shè)計(jì)、避免回表、刪除冗余索引等)的核心前提。下一篇我們會(huì)深入講解B+樹的底層原理,看看它是如何支撐聚簇索引和非聚簇索引高效工作的,以及如何基于B+樹設(shè)計(jì)更優(yōu)的索引方案~
到此這篇關(guān)于MySQL 聚簇索引、非聚簇索引與回表的文章就介紹到這了,更多相關(guān)mysql聚簇索引、非聚簇索引與回表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL優(yōu)化中B樹索引知識(shí)點(diǎn)總結(jié)
在本文里我們給大家整理了關(guān)于MySQL優(yōu)化中B樹索引的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們可以學(xué)習(xí)下。2019-02-02
CentOS7安裝MySQL8的超級(jí)詳細(xì)教程(無坑!)
我們在Linux系統(tǒng)中,如果要使用關(guān)系型數(shù)據(jù)庫的話,基本都是用的mysql,這篇文章主要給大家介紹了關(guān)于CentOS7安裝MySQL8的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
MySQL錯(cuò)誤:Can‘t?connect?to?MySQL?server?on?localhost解決辦法
這篇文章主要給大家介紹了關(guān)于MySQL錯(cuò)誤:Can‘t?connect?to?MySQL?server?on?localhost的解決辦法,文中介紹的方法分多種情況,通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05
MySQL聯(lián)合索引與最左匹配原則的實(shí)現(xiàn)
最左匹配原則在我們MySQL開發(fā)過程中和面試過程中經(jīng)常遇到,為了加深印象和理解,我在這里把MySQL的最左匹配原則詳細(xì)的講解一下,感興趣的可以了解一下2023-12-12
MySQL中使用JSON存儲(chǔ)數(shù)據(jù)的實(shí)現(xiàn)示例
本文主要介紹了MySQL中使用JSON存儲(chǔ)數(shù)據(jù)的實(shí)現(xiàn)示例,我們可以在MySQL中直接存儲(chǔ)、查詢和操作JSON數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
Mysql5.7.14安裝配置方法操作圖文教程(密碼問題解決辦法)
本篇文章主要涉及mysql5.7.14用以往的安裝方法安裝存在的密碼登錄不上,密碼失效等問題的解決辦法,需要的朋友參考下吧2017-01-01
Mysql LONGBLOB 類型存儲(chǔ)二進(jìn)制數(shù)據(jù) (修改+調(diào)試+整理)
代碼來自網(wǎng)絡(luò),我學(xué)習(xí)整理了一下,測試通過,下面的參數(shù)需要設(shè)置為你自己的2009-07-07

