MySQL索引的原理與性能優(yōu)化設計教程(圖文代碼)
一、前言
索引是一種用于快速查詢和檢索數(shù)據(jù)的數(shù)據(jù)結構,其本質(zhì)可以看成是一種排序好的數(shù)據(jù)結構。
索引的作用就相當于書的目錄。打個比方:我們在查字典的時候,如果沒有目錄,那我們就只能一頁一頁地去找我們需要查的那個字,速度很慢;如果有目錄了,我們只需要先去目錄里查找字的位置,然后直接翻到那一頁就行了。
索引底層數(shù)據(jù)結構存在很多種類型,常見的索引結構有:B 樹、 B+ 樹 和 Hash、紅黑樹。在 MySQL 中,無論是 Innodb 還是 MyISAM,都使用了 B+ 樹作為索引結構。
二、索引類型劃分
按照數(shù)據(jù)結構維度劃分:
BTree 索引:MySQL 里默認和最常用的索引類型。只有葉子節(jié)點存儲 value,非葉子節(jié)點只有指針和 key。存儲引擎 MyISAM 和 InnoDB 實現(xiàn) BTree 索引都是使用 B+Tree,但二者實現(xiàn)方式不一樣(前面已經(jīng)介紹了)。
哈希索引:類似鍵值對的形式,一次即可定位。
RTree 索引:一般不會使用,僅支持 geometry 數(shù)據(jù)類型,優(yōu)勢在于范圍查找,效率較低,通常使用搜索引擎如 ElasticSearch 代替。
全文索引:對文本的內(nèi)容進行分詞,進行搜索。目前只有
CHAR、VARCHAR、TEXT列上可以創(chuàng)建全文索引。一般不會使用,效率較低,通常使用搜索引擎如 ElasticSearch 代替。
按數(shù)據(jù)結構維度劃分的索引類型本文不做詳細介紹,本文主要針對以下兩種分類做闡述
按“功能/約束”分類:主鍵索引、唯一索引、常規(guī)索引、全文索引
| 分類 | 含義 | 特點 | 關鍵字 |
|---|---|---|---|
| 主鍵索引 | 針對于表中主鍵創(chuàng)建的索引 | 默認自動創(chuàng)建, 只能有一個 | PRIMARY |
| 唯一索引 | 避免同一個表中某數(shù)據(jù)列中的值重復 | 可以有多個 | UNIQUE |
| 常規(guī)索引 | 快速定位特定數(shù)據(jù) | 可以有多個 | |
| 全文索引 | 全文索引查找的是文本中的關鍵詞,而不是比較索引中的值 | 可以有多個 | FULLTEXT |
按“存儲形式/數(shù)據(jù)組織方式”分類:聚集索引(Clustered)、二級索引(Secondary)
| 分類 | 含義 | 特點 |
|---|---|---|
| 聚集索引(Clustered Index) | 將數(shù)據(jù)存儲與索引放到了一塊,索引結構的葉子節(jié)點保存了行數(shù)據(jù) | 必須有,而且只有一個 |
| 二級索引(Secondary Index) | 將數(shù)據(jù)與索引分開存儲,索引結構的葉子節(jié)點關聯(lián)的是對應的主鍵 | 可以存在多個 |
三、按功能/約束分類
1.主鍵索引(PRIMARY KEY)
定義
表的“主鍵”對應的索引,MySQL 用它來唯一標識一行數(shù)據(jù)。
一張表 只能有一個主鍵,主鍵列 不能為 NULL,并且 必須唯一。
InnoDB 特點
InnoDB 中 主鍵索引 = 聚集索引(后面會解釋聚集索引是什么)。
也就是說:數(shù)據(jù)行本身就“存”在主鍵索引的 B+Tree 葉子節(jié)點里。
適用場景
絕大多數(shù)表都應該設計主鍵:自增 id、雪花 id、UUID(不推薦隨機 UUID 做主鍵,容易導致頁分裂/碎片)。
必須為表指定主鍵(如無顯式定義,InnoDB 會自動生成隱藏主鍵)。
常用于
WHERE user_id = 1001或聯(lián)表查詢。
CREATE TABLE users ( id INT PRIMARY KEY, -- 主鍵索引 username VARCHAR(50) );
2.唯一索引(UNIQUE)
定義
約束某列/多列的值必須唯一。
限制列值 不能重復(但大多數(shù)情況下允許 NULL,且多個 NULL 在 MySQL/InnoDB 中通常是允許的)。
一張表可以有多個唯一索引。
底層實現(xiàn)
InnoDB 下通常也是 B+Tree。
與普通索引的核心差別:寫入時會做 唯一性校驗。
典型用途
既是“約束”(保證不重復),也是“加速器”(加速查詢)
常用于:手機號、郵箱、業(yè)務唯一編號
用戶表
username、email;業(yè)務表的唯一業(yè)務號(如order_no)。
CREATE TABLE users ( id INT PRIMARY KEY, mobile VARCHAR(20) UNIQUE, -- 唯一索引 email VARCHAR(50) UNIQUE );
3.常規(guī)索引(普通索引 / INDEX)
定義
最基本的索引,只用于加速查詢,沒有唯一性約束。
底層實現(xiàn)
InnoDB:一般是 B+Tree 二級索引(后面會講“二級索引”)。
特點
最常用:按條件查、排序、范圍查詢、JOIN
可以是單列索引,也可以是聯(lián)合索引(復合索引)
用途
頻繁作為
WHERE條件、JOIN條件、ORDER BY、GROUP BY的列。支持
WHERE status = 'paid'或ORDER BY create_time。
例子:單列索引
ALTER TABLE user ADD INDEX idx_name(name);
例子:聯(lián)合索引
ALTER TABLE user ADD INDEX idx_name_phone(name, phone);
查詢:
SELECT * FROM user WHERE name='Tom' AND phone='138...';
?? 更容易走 (name, phone) 的聯(lián)合索引。
4.全文索引(FULLTEXT)
定義
InnoDB 從 MySQL 5.6 開始支持 FULLTEXT(歷史上 MyISAM 更早支持)。
適合:分詞 + 相關性排序
用來做“文本檢索”,支持對長文本按詞(或按分詞)搜索,例如
MATCH(col) AGAINST(...)。
實現(xiàn)與特點
InnoDB 的 FULLTEXT 是專門的倒排索引體系(不是 B+Tree)。
更適合:文章、評論、商品描述等“文本搜索”。
注意:中文檢索通常需要分詞支持(MySQL 原生能力有限,很多場景會用 Elasticsearch 等專用搜索引擎)。
例子
CREATE TABLE article ( id BIGINT PRIMARY KEY, title VARCHAR(200), content TEXT, FULLTEXT KEY ft_content(content) ) ENGINE=InnoDB;
查詢:
SELECT * FROM article
WHERE MATCH(content) AGAINST('mysql 索引' IN NATURAL LANGUAGE MODE);四、按“存儲形式/數(shù)據(jù)組織方式”分類
1.聚簇索引(聚集索引)
聚簇索引:在 InnoDB 存儲引擎中,聚簇索引通常就是主鍵索引 。聚簇索引的特點是數(shù)據(jù)與索引一體化存儲,即數(shù)據(jù)行(整行,不是指針)直接存儲在索引的葉子節(jié)點中,并且數(shù)據(jù)按照主鍵的順序進行物理存儲 。這使得聚簇索引在查詢時具有極高的效率,尤其是對于主鍵查詢和范圍查詢。因為數(shù)據(jù)是按照主鍵順序存儲的,所以在進行范圍查詢(如查詢 ID 在某個范圍內(nèi)的用戶)時,可以利用索引的有序性,快速定位到滿足條件的數(shù)據(jù)。此外,聚簇索引還能利用順序檢測預取機制,提高數(shù)據(jù)讀取的效率。例如,在一個用戶表中,以用戶 ID 為主鍵創(chuàng)建聚簇索引,當查詢用戶 ID 為 100 的用戶信息時,數(shù)據(jù)庫可以直接通過聚簇索引找到對應的葉子節(jié)點,獲取用戶信息,無需進行額外的查找操作。需要注意的是,一張表只能有一個聚簇索引,因為數(shù)據(jù)的物理存儲順序只能有一種。
概括:用主鍵查 = 直接定位到葉子節(jié)點 = 一次 B+Tree 查找拿到整行。
為什么叫“聚集”
因為數(shù)據(jù)行與索引鍵“聚在一起”,數(shù)據(jù)就是索引的一部分。
聚集索引選取規(guī)則:
如果存在主鍵,主鍵索引就是聚集索引。
如果不存在主鍵,將使用第一個唯一(UNIQUE)索引作為聚集索引。
如果表沒有主鍵,或沒有合適的唯一索引,則InnoDB會自動生成一個rowid作為隱藏的聚集索引。
聚簇索引的優(yōu)缺點:
優(yōu)點:
查詢速度非???/strong>:聚簇索引的查詢速度非常的快,因為整個 B+ 樹本身就是一顆多叉平衡樹,葉子節(jié)點也都是有序的,定位到索引的節(jié)點,就相當于定位到了數(shù)據(jù)。相比于非聚簇索引, 聚簇索引少了一次讀取數(shù)據(jù)的 IO 操作。
對排序查找和范圍查找優(yōu)化:聚簇索引對于主鍵的排序查找和范圍查找速度非???。
缺點:
依賴于有序的數(shù)據(jù):因為 B+ 樹是多路平衡樹,如果索引的數(shù)據(jù)不是有序的,那么就需要在插入時排序,如果數(shù)據(jù)是整型還好,否則類似于字符串或 UUID 這種又長又難比較的數(shù)據(jù),插入或查找的速度肯定比較慢。
更新代價大:如果對索引列的數(shù)據(jù)被修改時,那么對應的索引也將會被修改,而且聚簇索引的葉子節(jié)點還存放著數(shù)據(jù),修改代價肯定是較大的,所以對于主鍵索引來說,主鍵一般都是不可被修改的。
2.非聚簇索引
也稱為二級索引,其索引存儲的是主鍵值,而不是實際的數(shù)據(jù)行 。當使用非聚簇索引進行查詢時,首先會根據(jù)索引找到對應的主鍵值,然后再通過主鍵值在聚簇索引中查找實際的數(shù)據(jù)行(整行),這個過程稱為回表查詢 。非聚簇索引支持多列組合索引,適用于多個字段聯(lián)合查詢的場景。例如,在一個訂單表中,經(jīng)常需要根據(jù)客戶 ID 和訂單日期進行查詢,可以為客戶 ID 和訂單日期創(chuàng)建組合非聚簇索引。雖然非聚簇索引需要回表查詢,查詢效率相對聚簇索引略低,但在某些情況下,它可以提供更靈活的查詢方式。比如,在查詢訂單表中某個客戶在特定日期之后的訂單時,通過組合非聚簇索引可以快速定位到滿足條件的主鍵值,然后再通過回表查詢獲取完整的訂單信息。
概括:二級索引的葉子節(jié)點存的不是整行,而是“索引列 + 主鍵值”。
覆蓋索引(避免回表)
如果查詢需要的列 都在二級索引里(或索引里包含它們),那就不需要回表,這叫 覆蓋索引。
例如:
select name from user where age=20;若建了
(age, name)聯(lián)合索引,就可能直接從索引葉子拿到name,無需回表。
非聚簇索引的優(yōu)缺點:
優(yōu)點:
更新代價比聚簇索引要小。非聚簇索引的更新代價就沒有聚簇索引那么大了,非聚簇索引的葉子節(jié)點是不存放數(shù)據(jù)的。
缺點:
依賴于有序的數(shù)據(jù):跟聚簇索引一樣,非聚簇索引也依賴于有序的數(shù)據(jù)。
可能會二次查詢(回表):這應該是非聚簇索引最大的缺點了。當查到索引對應的指針或主鍵后,可能還需要根據(jù)指針或主鍵再到數(shù)據(jù)文件或表中查詢。

五、圖解
1.示例表結構
CREATE TABLE account_example ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '賬戶ID', name VARCHAR(20) COMMENT '姓名', money DOUBLE(10,2) COMMENT '余額', INDEX idx_name (name), INDEX idx_name_money (name, money) ) ENGINE=InnoDB; ? -- 插入數(shù)據(jù) insert into account_example(id, name , money) values (1, '張三', 100.00), (3, '李四', 200.00), (7, '王五', 300.00);
id→ 主鍵索引(聚簇索引)
idx_name(name)→ 普通二級索引
idx_name_money(name, money)→ 聯(lián)合索引

2.聚簇索引(id)B+Tree 示意圖:
聚簇索引的B+Tree 的葉子節(jié)點存放的是整行數(shù)據(jù)。

特點總結
一張表 只能有一個聚集索引
數(shù)據(jù)行 按主鍵順序物理存儲
使用主鍵查詢:
SELECT * FROM account WHERE id = 3;
?? 一次 B+Tree 查找即可拿到整行數(shù)據(jù),不存在回表
3.非聚簇索引(idx_name)的 B+Tree 示意圖:
非聚簇索引的葉子節(jié)點中:不存整行數(shù)據(jù),只存「索引列值 + 主鍵值」

二級索引的葉子節(jié)點下掛的是該字段值對應的主鍵值。
4.為什么會發(fā)生回表
示例 SQL(會回表)
SELECT * FROM account WHERE name = '李四';
執(zhí)行過程拆解
Step 1:通過二級索引 idx_name 查找
name='李四' ↓ 在 idx_name B+Tree 中定位 ↓ 得到主鍵 id = 3
Step 2:根據(jù)主鍵回到聚集索引
id=3 ↓ 在 PRIMARY KEY B+Tree 中查找 ↓ 獲取完整行數(shù)據(jù)
?? 這一步稱為:回表
本質(zhì)原因:二級索引不存完整數(shù)據(jù)
5.聯(lián)合索引 idx(name, money) 的存儲結構
聯(lián)合索引葉子節(jié)點存:(name, money) + id
idx_name_money(name, money) B+Tree 示意圖:

6.覆蓋索引 vs 回表
? 示例 1:需要回表
SELECT * FROM account WHERE name = '李四';
使用
idx_namemoney 不在索引中
必須回表
? 示例 2:覆蓋索引(不回表)
SELECT name, money FROM account WHERE name = '李四';
使用
idx_name_money查詢字段 全部存在索引葉子節(jié)點
無需回表
?? 這就叫:覆蓋索引(Covering Index)
7.總結
| 場景 | 是否回表 | 原因 |
|---|---|---|
| where id = ? | ? | 聚集索引葉子節(jié)點存整行 |
| 普通二級索引查 * | ? | 二級索引不存完整數(shù)據(jù) |
| 聯(lián)合索引覆蓋查詢字段 | ? | 查詢字段都在索引中 |
| SELECT * | 幾乎一定 | 字段太多,無法覆蓋 |
六、關聯(lián)
| 功能分類 | 在 InnoDB 的存儲形式 |
|---|---|
| 主鍵索引 | 聚集索引(葉子存整行) |
| 唯一索引 | 通常是 二級索引(除非它被選為聚集索引鍵) |
| 常規(guī)索引 | 二級索引 |
| 全文索引 | 倒排索引體系(不按聚集/二級的 B+Tree 邏輯走) |
七、問題
主鍵為什么不要用隨機值(如隨機 UUID)?
聚集索引決定了數(shù)據(jù)行的物理組織順序
隨機插入會導致頻繁頁分裂、碎片、寫放大,性能更差 (自增/有序主鍵插入更“順滑”)
二級索引為什么存主鍵值,而不是物理地址?
因為 InnoDB 的數(shù)據(jù)行會移動/頁會分裂,存物理地址維護成本高
存主鍵值更穩(wěn)定,代價是可能需要回表
建索引的常見收益點
加速
WHERE、JOIN、ORDER BY、GROUP BY利用覆蓋索引減少回表、減少 IO
八、總結
InnoDB 中,主鍵索引就是聚集索引,數(shù)據(jù)行存放在主鍵 B+Tree 的葉子節(jié)點;
二級索引的葉子節(jié)點只保存索引列和主鍵值,因此在查詢非索引字段時需要回表;
當查詢字段完全被索引覆蓋時,可以避免回表,從而顯著提升查詢性能。
到此這篇關于MySQL索引的原理與性能優(yōu)化教程的文章就介紹到這了,更多相關索引設計優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解析SQL Server 視圖、數(shù)據(jù)庫快照
在程序開發(fā)過程中,任何一個項目都離不開數(shù)據(jù)庫,這篇文章給大家詳細介紹SQL Server 視圖、數(shù)據(jù)庫快照相關內(nèi)容,需要的朋友可以參考下2015-08-08

