前言
數據庫中的索引是爲了提高查詢效率的,將像字典的目錄一樣。
當我們瞭解索引的原理之後,就沒有必要去死記硬背所謂的 Mysql 軍規之類的東西了。
本文內容
-
索引的類型:UNIQUE,FULLTEXT,SPATIAL,NORMAL(普通索引) -
索引爲什麼會採用 B+ 樹結構,爲什麼不是二叉樹、B- 樹 -
Mysql 中 B+ 樹索引 和 Hash 索引應該選哪個 -
爲什麼索引的使用需要遵循 最左匹配原則
-
聯合索引
、聚簇索引
和覆蓋索引
分別是什麼 -
索引添加的判斷依據是什麼
索引
Mysql 中常見的索引類型有:
-
普通索引 -
唯一索引 -
全文索引 -
空間索引
Mysql 中索引的數據結構有:
-
B+Tree
,存儲引擎InnoDB
和MyISAM
都支持。因爲我們一般都是使用存儲引擎InnoDB
和MyISAM
,我們都是使用B+Tree
數據結構的索引。 -
HASH
,存儲引擎MEMORY
支持,存儲引擎InnoDB
和MyISAM
不能手動定義 HASH 索引。
因此,我們詳細瞭解 B+Tree
就行了。
我們先來介紹一下兩種索引的數據結構的區別,感受一些各自的使用場景。
Hash 數據結構的索引
Hash 數據結構,我們可以理解爲 Java 中的 Map,存儲的都是 key-value 鍵值對的數據,但我們沒有辦法進行範圍查找。但是它的等值查找比較快,時間複雜度 O(1)。
創建一張表,字段有 id 和 description,並且在 description 上添加 HASH 索引。存儲引擎使用 MEMORY
DROP TABLE IF EXISTS `index_hash_test`;
CREATE TABLE `index_hash_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `inx_descrption` (`description`(100)) USING HASH
) ENGINE=MEMORY AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
INSERT INTO `index_hash_test` VALUES (1, 'a');
INSERT INTO `index_hash_test` VALUES (3, 'b');
INSERT INTO `index_hash_test` VALUES (2, 'c');
INSERT INTO `index_hash_test` VALUES (4, 'd');
INSERT INTO `index_hash_test` VALUES (5, 'e');
HASH 索引範圍查找不生效
-- 查看執行計劃看到,全表掃描,沒有走索引
EXPLAIN SELECT * FROM index_hash_test WHERE description >= 'b'
HASH 等值查找生效
EXPLAIN SELECT * FROM index_hash_test WHERE description = 'b'
B+Tree 數據結構的索引
B+Tree
數據結構的索引是可以進行範圍查詢的。
DROP TABLE IF EXISTS `index_test`;
CREATE TABLE `index_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `inx_descrption` (`description`)USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `index_test` VALUES (1, 'a');
INSERT INTO `index_test` VALUES (3, 'b');
INSERT INTO `index_test` VALUES (2, 'c');
INSERT INTO `index_test` VALUES (4, 'd');
INSERT INTO `index_test` VALUES (5, 'e');
在 B+Tree
數據結構的索引表上執行查詢計劃,可以看到在查詢的時候,索引可以使用。
EXPLAIN SELECT * FROM index_test WHERE description >= 'b';
EXPLAIN SELECT * FROM index_test WHERE description > 'b';
EXPLAIN SELECT * FROM index_test WHERE description = 'b';
分別查看執行計劃可以看到,等值查找和範圍查找都使用到了索引,但是這三者性能上會有所差別 (以後會詳細介紹這部分內容)。
B+Tree 索引數據結構
實際開發中,我們使用的存儲引擎是 InnoDB
和 MyISAM
,因此主要研究 B+Tree
索引
感興趣的話可以在這個網站,看數據結構是怎樣運行的。比如說 B+ 樹插入、刪除和查詢
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
相較於二叉樹,B+ 樹子節點會更多,樹的高度會更低,在查找數據的時候,減少了遍歷的次數以達到可以減少 Io 次數 (從磁盤加載數據到內存中)。
B+ 樹相較於 B- 樹,葉子節點是有序的,並且只有葉子節點會存數據。
比如查詢大於 3 的數據的時候,找到了 3 直接遍歷 3 上的鏈表就可以查詢大於3 的數據。
一個表上索引也不是越多越好,通常推薦不超過 5 個索引。因爲我們修改數據的時候,數據庫爲了維護索引的數據結構也會產生計算和 io 從而影響數據的性能。當索引多的時候,數據量大的時候,這部分的影響就可以體現出來了。
當因爲業務需要,添加的索引超過了 5 個,並且通過壓測確定是索引過多影響了數據庫的性能。可以考慮對錶進行垂直拆分,將一部分業務字段拆分到另一個表中去。
索引相關名詞
CREATE TABLE `my_test` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` varchar(200) DEFAULT NULL,
`phone` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `index_a_b` (`name`(20),`age`(5)) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `my_test` VALUES (1, 'a', '1');
INSERT INTO `my_test` VALUES (2, 'a', '2');
INSERT INTO `my_test` VALUES (3, 'a', '3');
INSERT INTO `my_test` VALUES (4, 'b', '1');
INSERT INTO `my_test` VALUES (5, 'b', '2');
關於索引的圖只是示意性展示,重在理解。
聚簇索引
聚簇索引(也可以簡單理解爲主鍵)的葉子節點存的是整行數據,而非聚簇索引的葉子節點存的是索引數據和主鍵。
非葉子節點都是主鍵,葉子節點中既有主鍵對應的行數據。
當使用主鍵查詢數據的時候,實際就是查詢聚簇索引,然後從其中把數據讀出來返回。
聯合索引
聯合索引:就是多個列組成一個索引。比如 name 和 age 組成一個索引
我們看到聯合索引是按照 name,age 進行排序的,當 name 一樣的時候,按 age 排序,並且葉子節點會有 id 的數據。
通過查詢這個索引就可以得到對應數據行的數據主鍵,然後根據主鍵 id 查詢聚簇索引得到整行數據。
覆蓋索引
覆蓋索引,也是聯合索引。只是我們查詢的數據就在索引中,不用再去 回表
查找數據了。
SELECT `name`,age FROM my_test WHERE `name` = b;
上述 sql 執行就利用了覆蓋索引,查詢的結果就在索引中。
SELECT * FROM my_test WHERE `name` = b;
索引中的數據只有 id,name,age,差 phone。這個數據只能通過回表去查詢數據。
先查詢 (name,age) 這個索引拿到主鍵 id,在通過主鍵 id 去聚簇索引中查詢數據。
也有人經常推薦說,查詢的時候要查詢需要的字段,不要使用 select * ,這樣做的好處一是減少 io,另一個就是避免回表。
Mysql 與磁盤交互的最小單位是 Page, B+ 樹 中存儲的實際是一頁一頁的數據,下面這張圖可以近似理解。
圖來自《MySQL 是怎樣運行的:從根兒上理解 MySQL》
索引的使用和添加
瞭解了 B+ 樹索引數據結構,我們也就差不多知道怎樣使用索引了,也可以理解使用索引的一些規則。
通常說的索引失效,一部分是可以從數據結構來推算出來的;另一部分就是 mysql 通過自身查詢統計的數據判斷不走索引性能會更高而導致索引失效而去全表掃描。
索引是爲了我們從表中檢索出少量數據才使用的。如果你添加了索引,當查詢的時候還需要掃描表中絕大數的數據,就不用在這個字段添加索引了,因爲這對你的查詢沒有任何提高,反而因爲數據的修改需要維護索引,可能還降低了查詢性能。
通常我們會選擇區分度比較高的字段添加索引(如果這個字段和查詢業務沒有關係也沒有必要添加索引)。
比如說性別這個字段,只有男、女兩個選項,當你有 1000 w 的數據的時候,700w 是 男,300w 是女,就沒必要添加索引了,沒有意義。性別這個字段區分度較低。
SHOW INDEX FROM index_test;
當我們創建索引之後,可以通過查看 Cardinality
來判斷索引添加是否合理。Cardinality/表總行數
值越接近 1 查詢性能越好。
Cardinality
代表的是這個索引的數據唯一值的個數。現在表中有 5 行數據,並且 description
的值沒有重複的。
數據庫也會根據 Cardinality
進行優化查詢,但這個值又不是實時更新的,我們需要每過一段時間,在業務不忙的時候更新這個值。
ANALYZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE tbl_name [, tbl_name] ...
mysql> analyze table index_test;
+-----------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-----------------------+---------+----------+----------+
| index_blog.index_test | analyze | status | OK |
+-----------------------+---------+----------+----------+
1 row in set (0.00 sec)
最左匹配規則
我們在使用索引的時候,只需要包含索引的最左邊就可以匹配索引(name,age)
SELECT * FROM my_test WHERE `name` = 'b';
SELECT * FROM my_test WHERE `name` = 'b' AND age = 1;
當我們執行下面的 sql 的時候,就用不到索引 (name,age)
-- 通過查看執行計劃,看查詢的性能:全表掃描
EXPLAIN SELECT * FROM my_test WHERE age=1;
最左匹配原則
是由 Mysql 的索引的數據結構決定的。
聯合索引 (name,age)
的 B+Tree
數據結構中葉子節點是按照 name 排序再按照 age 排序。age 實際是亂序的,沒有辦法進行範圍查找。
如果你還想在 age 進行索引查找,就需要在 age 上建立一個新索引。
-- 全表掃描
EXPLAIN SELECT * FROM my_test WHERE NAME LIKE '%a';
-- 索引範圍查找
EXPLAIN SELECT * FROM my_test WHERE NAME LIKE 'a%';
索引的建立,不會整個字段值都參與索引的建立,一般會指定多長的字段(從值開頭部分的長度)參與索引的建立。
當你需要關鍵字查找的時候,可以使用全文索引,或者是增加一個 ES 用於檢索。
本文分享自微信公衆號 - Mflyyou(Mflyyou)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。