共計 16488 個字符,預計需要花費 42 分鐘才能閱讀完成。
自動寫代碼機器人,免費開通
這篇文章給大家介紹 MySQL 中如何使用索引,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
mysql 采用 b + 樹的方式存儲索引信息。
b+ 樹結構如下:
說一下 b + 樹的幾個特點:
葉子節點(最下面的一層)存儲關鍵字(索引字段的值)信息及對應的 data,葉子節點存儲了所有記錄的關鍵字信息
其他非葉子節點只存儲關鍵字的信息及子節點的指針
每個葉子節點相當于 mysql 中的一頁,同層級的葉子節點以雙向鏈表的形式相連
每個節點(頁)中存儲了多條記錄,記錄之間用單鏈表的形式連接組成了一條有序的鏈表,順序是按照索引字段排序的
b+ 樹中檢索數據時:每次檢索都是從根節點開始,一直需要搜索到葉子節點
InnoDB 的數據是按數據頁為單位來讀寫的。也就是說,當需要讀取一條記錄的時候,并不是將這個記錄本身從磁盤讀取出來,而是以頁為單位,將整個也加載到內存中,一個頁中可能有很多記錄,然后在內存中對頁進行檢索。在 innodb 中,每個頁的大小默認是 16kb。
Mysql 中索引分為
聚集索引(主鍵索引)
每個表一定會有一個聚集索引,整個表的數據存儲以 b + 樹的方式存在文件中,b+ 樹葉子節點中的 key 為主鍵值,data 為完整記錄的信息;非葉子節點存儲主鍵的值。
通過聚集索引檢索數據只需要按照 b + 樹的搜索過程,即可以檢索到對應的記錄。
非聚集索引
每個表可以有多個非聚集索引,b+ 樹結構,葉子節點的 key 為索引字段字段的值,data 為主鍵的值;非葉子節點只存儲索引字段的值。
通過非聚集索引檢索記錄的時候,需要 2 次操作,先在非聚集索引中檢索出主鍵,然后再到聚集索引中檢索出主鍵對應的記錄,該過程比聚集索引多了一次操作。
索引怎么走,為什么有些查詢不走索引?為什么使用函數了數據就不走索引了?
這些問題可以先放一下,我們先看一下 b + 樹檢索數據的過程,這個屬于原理的部分,理解了 b + 樹各種數據檢索過程,上面的問題就都可以理解了。
通常說的這個查詢走索引了是什么意思?
當我們對某個字段的值進行某種檢索的時候,如果這個檢索過程中,我們能夠快速定位到目標數據所在的頁,有效的降低頁的 io 操作,而不需要去掃描所有的數據頁的時候,我們認為這種情況能夠有效的利用索引,也稱這個檢索可以走索引,如果這個過程中不能夠確定數據在那些頁中,我們認為這種情況下索引對這個查詢是無效的,此查詢不走索引。
b+ 樹中數據檢索過程
唯一記錄檢索
如上圖,所有的數據都是唯一的,查詢 105 的記錄,過程如下:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
將 P1 頁加載到內存
在內存中采用二分法查找,可以確定 105 位于 [100,150) 中間,所以我們需要去加載 100 關聯 P4 頁
將 P4 加載到內存中,采用二分法找到 105 的記錄后退出
查詢某個值的所有記錄
如上圖,查詢 105 的所有記錄,過程如下:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
將 P1 頁加載到內存
在內存中采用二分法查找,可以確定 105 位于 [100,150) 中間,100 關聯 P4 頁
將 P4 加載到內存中,采用二分法找到最有一個小于 105 的記錄,即 100,然后通過鏈表從 100 開始向后訪問,找到所有的 105 記錄,直到遇到第一個大于 100 的值為止
范圍查找
數據如上圖,查詢 [55,150] 所有記錄,由于頁和頁之間是雙向鏈表升序結構,頁內部的數據是單項升序鏈表結構,所以只用找到范圍的起始值所在的位置,然后通過依靠鏈表訪問兩個位置之間所有的數據即可,過程如下:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
將 P1 頁加載到內存
內存中采用二分法找到 55 位于 50 關聯的 P3 頁中,150 位于 P5 頁中
將 P3 加載到內存中,采用二分法找到第一個 55 的記錄,然后通過鏈表結構繼續向后訪問 P3 中的 60、67,當 P3 訪問完畢之后,通過 P3 的 nextpage 指針訪問下一頁 P4 中所有記錄,繼續遍歷 P4 中的所有記錄,直到訪問到 P5 中所有的 150 為止。
模糊匹配
數據如上圖。
查詢以 `f` 開頭的所有記錄
過程如下:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
將 P1 數據加載到內存中
在 P1 頁的記錄中采用二分法找到最后一個小于等于 f 的值,這個值是 f,以及第一個大于 f 的,這個值是 z,f 指向葉節點 P3,z 指向葉節點 P6,此時可以斷定以 f 開頭的記錄可能存在于 [P3,P6) 這個范圍的頁內,即 P3、P4、P5 這三個頁中
3. 加載 P3 這個頁,在內部以二分法找到第一條 f 開頭的記錄,然后以鏈表方式繼續向后訪問 P4、P5 中的記錄,即可以找到所有已 f 開頭的數據
查詢包含 `f` 的記錄
包含的查詢在 sql 中的寫法是 %f%,通過索引我們還可以快速定位所在的頁么?
可以看一下上面的數據,f 在每個頁中都存在,我們通過 P1 頁中的記錄是無法判斷包含 f 的記錄在那些頁的,只能通過 io 的方式加載所有葉子節點,并且遍歷所有記錄進行過濾,才可以找到包含 f 的記錄。
所以如果使用了 % 值 % 這種方式,索引對查詢是無效的。
最左匹配原則
當 b + 樹的數據項是復合的數據結構,比如 (name,age,sex) 的時候,b+ 樹是按照從左到右的順序來建立搜索樹的,比如當 (張三,20,F) 這樣的數據來檢索的時候,b+ 樹會優先比較 name 來確定下一步的所搜方向,如果 name 相同再依次比較 age 和 sex,最后得到檢索的數據;但當 (20,F) 這樣的沒有 name 的數據來的時候,b+ 樹就不知道下一步該查哪個節點,因為建立搜索樹的時候 name 就是第一個比較因子,必須要先根據 name 來搜索才能知道下一步去哪里查詢。比如當 (張三,F) 這樣的數據來檢索時,b+ 樹可以用 name 來指定搜索方向,但下一個字段 age 的缺失,所以只能把名字等于張三的數據都找到,然后再匹配性別是 F 的數據了,這個是非常重要的性質,即索引的最左匹配特性。
來一些示例我們體驗一下。
下圖中是 3 個字段 (a,b,c) 的聯合索引,索引中數據的順序是以 a asc,b asc,c asc 這種排序方式存儲在節點中的,索引先以 a 字段升序,如果 a 相同的時候,以 b 字段升序,b 相同的時候,以 c 字段升序,節點中每個數據認真看一下。
查詢 a = 1 的記錄
由于頁中的記錄是以 a asc,b asc,c asc 這種排序方式存儲的,所以 a 字段是有序的,可以通過二分法快速檢索到,過程如下:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
將 P1 加載到內存中
在內存中對 P1 中的記錄采用二分法找,可以確定 a = 1 的記錄位于 {1,1,1} 和{1,5,1}關聯的范圍內,這兩個值子節點分別是 P2、P4
加載葉子節點 P2,在 P2 中采用二分法快速找到第一條 a = 1 的記錄,然后通過鏈表向下一條及下一頁開始檢索,直到在 P4 中找到第一個不滿足 a = 1 的記錄為止
查詢 a =1 and b= 5 的記錄
方法和上面的一樣,可以確定 a =1 and b= 5 的記錄位于 {1,1,1} 和{1,5,1}關聯的范圍內,查找過程和 a = 1 查找步驟類似。
查詢 b = 1 的記錄
這種情況通過 P1 頁中的記錄,是無法判斷 b = 1 的記錄在那些頁中的,只能加鎖索引樹所有葉子節點,對所有記錄進行遍歷,然后進行過濾,此時索引是無效的。
按照 c 的值查詢
這種情況和查詢 b = 1 也一樣,也只能掃描所有葉子節點,此時索引也無效了。
按照 b 和 c 一起查
這種也是無法利用索引的,也只能對所有數據進行掃描,一條條判斷了,此時索引無效。
按照 [a,c] 兩個字段查詢
這種只能利用到索引中的 a 字段了,通過 a 確定索引范圍,然后加載 a 關聯的所有記錄,再對 c 的值進行過濾。
查詢 a =1 and b =0 and c= 1 的記錄
這種情況只能先確定 a =1 and b = 0 所在頁的范圍,然后對這個范圍的所有頁進行遍歷,c 字段在這個查詢的過程中,是無法確定 c 的數據在哪些頁的,此時我們稱 c 是不走索引的,只有 a、b 能夠有效的確定索引頁的范圍。
類似這種的還有、、between and,多字段索引的情況下,mysql 會一直向右匹配直到遇到范圍查詢 (、、between、like) 就停止匹配。
上面說的各種情況,大家都多看一下圖中數據,認真分析一下查詢的過程,基本上都可以理解了。
上面這種查詢叫做最左匹配原則。
索引區分度
我們看 2 個有序數組
[1,2,3,4,5,6,7,8,8,9,10][1,1,1,1,1,8,8,8,8,8]
上面 2 個數組是有序的,都是 10 條記錄,如果我需要檢索值為 8 的所有記錄,那個更快一些?
咱們使用二分法查找包含 8 的所有記錄過程如下:先使用二分法找到最后一個小于 8 的記錄,然后沿著這條記錄向后獲取下一個記錄,和 8 對比,知道遇到第一個大于 8 的數字結束,或者到達數組末尾結束。
采用上面這種方法找到 8 的記錄,第一個數組中更快的一些。因為第二個數組中含有 8 的比例更多的,需要訪問以及匹配的次數更多一些。
這里就涉及到數據的區分度問題:
索引區分度 = count(distint 記錄) / count(記錄)。
當索引區分度高的時候,檢索數據更快一些,索引區分度太低,說明重復的數據比較多,檢索的時候需要訪問更多的記錄才能夠找到所有目標數據。
當索引區分度非常小的時候,基本上接近于全索引數據的掃描了,此時查詢速度是比較慢的。
第一個數組索引區分度為 1,第二個區分度為 0.2,所以第一個檢索更快的一些。
所以我們創建索引的時候,盡量選擇區分度高的列作為索引。
正確使用索引
準備 400 萬測試數據
/* 建庫 javacode2018*/DROP DATABASE IF EXISTS javacode2018;CREATE DATABASE javacode2018;USE javacode2018;/* 建表 test1*/DROP TABLE IF EXISTS test1;CREATE TABLE test1 ( id INT NOT NULL COMMENT 編號 , name VARCHAR(20) NOT NULL COMMENT 姓名 , sex TINYINT NOT NULL COMMENT 性別,1:男,2:女 , email VARCHAR(50));/* 準備數據 */DROP PROCEDURE IF EXISTS proc1;DELIMITER $CREATE PROCEDURE proc1() BEGIN DECLARE i INT DEFAULT 1; START TRANSACTION; WHILE i = 4000000 DO INSERT INTO test1 (id, name, sex, email) VALUES (i,concat( javacode ,i),if(mod(i,2),1,2),concat(javacode ,i, @163.com SET ii = i + 1; if i%10000=0 THEN COMMIT; START TRANSACTION; END IF; END WHILE; COMMIT; END $ DELIMITER ; CALL proc1();
上面插入的 400 萬數據,除了 sex 列,其他列的值都是沒有重復的。
無索引檢索效果
400 萬數據,我們隨便查詢幾個記錄看一下效果。
按照 id 查詢記錄
mysql select * from test1 where id = 1; +----+-----------+-----+-------------------+ | id | name | sex | email | +----+-----------+-----+-------------------+ | 1 | javacode1 | 1 | javacode1@163.com | +----+-----------+-----+-------------------+ 1 row in set (1.91 sec)
id= 1 的數據,表中只有一行,耗時近 2 秒,由于 id 列無索引,只能對 400 萬數據進行全表掃描。
主鍵檢索
test1 表中沒有明確的指定主鍵,我們將 id 設置為主鍵:
mysql alter table test1 modify id int not null primary key; Query OK, 0 rows affected (10.93 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql show index from test1; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | test1 | 0 | PRIMARY | 1 | id | A | 3980477 | NULL | NULL | | BTREE | | | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 1 row in set (0.00 sec)
id 被置為主鍵之后,會在 id 上建立聚集索引,隨便檢索一條我們看一下效果:
mysql select * from test1 where id = 1000000; +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 1000000 | javacode1000000 | 2 | javacode1000000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
這個速度很快,這個走的是上面介紹的 ` 唯一記錄檢索 `。
between and 范圍檢索
mysql select count(*) from test1 where id between 100 and 110; +----------+ | count(*) | +----------+ | 11 | +----------+ 1 row in set (0.00 sec)
速度也很快,id 上有主鍵索引,這個采用的上面介紹的范圍查找可以快速定位目標數據。
但是如果范圍太大,跨度的 page 也太多,速度也會比較慢,如下:
mysql select count(*) from test1 where id between 1 and 2000000; +----------+ | count(*) | +----------+ | 2000000 | +----------+ 1 row in set (1.17 sec)
上面 id 的值跨度太大,1 所在的頁和 200 萬所在頁中間有很多頁需要讀取,所以比較慢。
所以使用 between and 的時候,區間跨度不要太大。
in 的檢索
in 方式檢索數據,我們還是經常用的。
平時我們做項目的時候,建議少用表連接,比如電商中需要查詢訂單的信息和訂單中商品的名稱,可以先查詢查詢訂單表,然后訂單表中取出商品的 id 列表,采用 in 的方式到商品表檢索商品信息,由于商品 id 是商品表的主鍵,所以檢索速度還是比較快的。
通過 id 在 400 萬數據中檢索 100 條數據,看看效果:
mysql select * from test1 a where a.id in (100000, 100001, 100002, 100003, 100004, 100005, 100006, 100007, 100008, 100009, 100010, 100011, 100012, 100013, 100014, 100015, 100016, 100017, 100018, 100019, 100020, 100021, 100022, 100023, 100024, 100025, 100026, 100027, 100028, 100029, 100030, 100031, 100032, 100033, 100034, 100035, 100036, 100037, 100038, 100039, 100040, 100041, 100042, 100043, 100044, 100045, 100046, 100047, 100048, 100049, 100050, 100051, 100052, 100053, 100054, 100055, 100056, 100057, 100058, 100059, 100060, 100061, 100062, 100063, 100064, 100065, 100066, 100067, 100068, 100069, 100070, 100071, 100072, 100073, 100074, 100075, 100076, 100077, 100078, 100079, 100080, 100081, 100082, 100083, 100084, 100085, 100086, 100087, 100088, 100089, 100090, 100091, 100092, 100093, 100094, 100095, 100096, 100097, 100098, 100099); +--------+----------------+-----+------------------------+ | id | name | sex | email | +--------+----------------+-----+------------------------+ | 100000 | javacode100000 | 2 | javacode100000@163.com | | 100001 | javacode100001 | 1 | javacode100001@163.com | | 100002 | javacode100002 | 2 | javacode100002@163.com | ....... | 100099 | javacode100099 | 1 | javacode100099@163.com | +--------+----------------+-----+------------------------+ 100 rows in set (0.00 sec)
耗時不到 1 毫秒,還是相當快的。
這個相當于多個分解為多個唯一記錄檢索,然后將記錄合并。
多個索引時查詢如何走?
我們在 name、sex 兩個字段上分別建個索引
mysql create index idx1 on test1(name); Query OK, 0 rows affected (13.50 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql create index idx2 on test1(sex); Query OK, 0 rows affected (6.77 sec) Records: 0 Duplicates: 0 Warnings: 0
看一下查詢:
mysql select * from test1 where name= javacode3500000 and sex=2; +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 3500000 | javacode3500000 | 2 | javacode3500000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
上面查詢速度很快,name 和 sex 上各有一個索引,覺得上面走哪個索引?
有人說 name 位于 where 第一個,所以走的是 name 字段所在的索引,過程可以解釋為這樣:
走 name 所在的索引找到 javacode3500000 對應的所有記錄
遍歷記錄過濾出 sex= 2 的值
我們看一下 name= javacode3500000 檢索速度,確實很快,如下:
mysql select * from test1 where name= javacode3500000 +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 3500000 | javacode3500000 | 2 | javacode3500000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
走 name 索引,然后再過濾,確實可以,速度也很快,果真和 where 后字段順序有關么?我們把 name 和 sex 的順序對調一下,如下:
mysql select * from test1 where sex=2 and name= javacode3500000 +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 3500000 | javacode3500000 | 2 | javacode3500000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
速度還是很快,這次是不是先走 sex 索引檢索出數據,然后再過濾 name 呢?我們先來看一下 sex= 2 查詢速度:
mysql select count(id) from test1 where sex=2; +-----------+ | count(id) | +-----------+ | 2000000 | +-----------+ 1 row in set (0.36 sec)
看上面,查詢耗時 360 毫秒,200 萬數據,如果走 sex 肯定是不行的。
我們使用 explain 來看一下:
mysql explain select * from test1 where sex=2 and name= javacode3500000 +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | test1 | NULL | ref | idx1,idx2 | idx1 | 62 | const | 1 | 50.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
possible_keys:列出了這個查詢可能會走兩個索引(idx1、idx2)
實際上走的卻是 idx1(key 列:實際走的索引)。
當多個條件中有索引的時候,并且關系是 and 的時候,會走索引區分度高的,顯然 name 字段重復度很低,走 name 查詢會更快一些。
模糊查詢
看兩個查詢
mysql select count(*) from test1 a where a.name like javacode1000% +----------+ | count(*) | +----------+ | 1111 | +----------+ 1 row in set (0.00 sec) mysql select count(*) from test1 a where a.name like %javacode1000% +----------+ | count(*) | +----------+ | 1111 | +----------+ 1 row in set (1.78 sec)
上面第一個查詢可以利用到 name 字段上面的索引,下面的查詢是無法確定需要查找的值所在的范圍的,只能全表掃描,無法利用索引,所以速度比較慢,這個過程上面有說過。
回表
當需要查詢的數據在索引樹中不存在的時候,需要再次到聚集索引中去獲取,這個過程叫做回表,如查詢:
mysql select * from test1 where name= javacode3500000 +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 3500000 | javacode3500000 | 2 | javacode3500000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
上面查詢是 *,由于 name 列所在的索引中只有 name、id 兩個列的值,不包含 sex、email,所以上面過程如下:
走 name 索引檢索 javacode3500000 對應的記錄,取出 id 為 3500000
在主鍵索引中檢索出 id=3500000 的記錄,獲取所有字段的值
索引覆蓋
查詢中采用的索引樹中包含了查詢所需要的所有字段的值,不需要再去聚集索引檢索數據,這種叫索引覆蓋。
我們來看一個查詢:
select id,name from test1 where name= javacode3500000
name 對應 idx1 索引,id 為主鍵,所以 idx1 索引樹葉子節點中包含了 name、id 的值,這個查詢只用走 idx1 這一個索引就可以了,如果 select 后面使用 *,還需要一次回表獲取 sex、email 的值。
所以寫 sql 的時候,盡量避免使用 *,* 可能會多一次回表操作,需要看一下是否可以使用索引覆蓋來實現,效率更高一些。
索引下推
簡稱 ICP,Index Condition Pushdown(ICP)是 MySQL 5.6 中新特性,是一種在存儲引擎層使用索引過濾數據的一種優化方式,ICP 可以減少存儲引擎訪問基表的次數以及 MySQL 服務器訪問存儲引擎的次數。
舉個例子來說一下:
我們需要查詢 name 以 javacode35 開頭的,性別為 1 的記錄數,sql 如下:
mysql select count(id) from test1 a where name like javacode35% and sex = 1; +-----------+ | count(id) | +-----------+ | 55556 | +-----------+ 1 row in set (0.19 sec)
過程:
走 name 索引檢索出以 javacode35 的第一條記錄,得到記錄的 id
利用 id 去主鍵索引中查詢出這條記錄 R1
判斷 R1 中的 sex 是否為 1,然后重復上面的操作,直到找到所有記錄為止。
上面的過程中需要走 name 索引以及需要回表操作。
如果采用 ICP 的方式,我們可以這么做,創建一個 (name,sex) 的組合索引,查詢過程如下:
走 (name,sex) 索引檢索出以 javacode35 的第一條記錄,可以得到(name,sex,id),記做 R1
判斷 R1.sex 是否為 1,然后重復上面的操作,知道找到所有記錄為止
這個過程中不需要回表操作了,通過索引的數據就可以完成整個條件的過濾,速度比上面的更快一些。
數字使字符串類索引失效
mysql insert into test1 (id,name,sex,email) values (4000001, 1 ,1, javacode2018@163.com Query OK, 1 row affected (0.00 sec) mysql select * from test1 where name = 1 +---------+------+-----+----------------------+ | id | name | sex | email | +---------+------+-----+----------------------+ | 4000001 | 1 | 1 | javacode2018@163.com | +---------+------+-----+----------------------+ 1 row in set (0.00 sec) mysql select * from test1 where name = 1; +---------+------+-----+----------------------+ | id | name | sex | email | +---------+------+-----+----------------------+ | 4000001 | 1 | 1 | javacode2018@163.com | +---------+------+-----+----------------------+ 1 row in set, 65535 warnings (3.30 sec)
上面 3 條 sql,我們插入了一條記錄。
第二條查詢很快,第三條用 name 和 1 比較,name 上有索引,name 是字符串類型,字符串和數字比較的時候,會將字符串強制轉換為數字,然后進行比較,所以第二個查詢變成了全表掃描,只能取出每條數據,將 name 轉換為數字和 1 進行比較。
數字字段和字符串比較什么效果呢?如下:
mysql select * from test1 where id = 4000000 +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 4000000 | javacode4000000 | 2 | javacode4000000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec) mysql select * from test1 where id = 4000000; +---------+-----------------+-----+-------------------------+ | id | name | sex | email | +---------+-----------------+-----+-------------------------+ | 4000000 | javacode4000000 | 2 | javacode4000000@163.com | +---------+-----------------+-----+-------------------------+ 1 row in set (0.00 sec)
id 上面有主鍵索引,id 是 int 類型的,可以看到,上面兩個查詢都非常快,都可以正常利用索引快速檢索,所以如果字段是數組類型的,查詢的值是字符串還是數組都會走索引。
函數使索引無效
mysql select a.name+1 from test1 a where a.name = javacode1 +----------+ | a.name+1 | +----------+ | 1 | +----------+ 1 row in set, 1 warning (0.00 sec) mysql select * from test1 a where concat(a.name, 1) = javacode11 +----+-----------+-----+-------------------+ | id | name | sex | email | +----+-----------+-----+-------------------+ | 1 | javacode1 | 1 | javacode1@163.com | +----+-----------+-----+-------------------+ 1 row in set (2.88 sec)
name 上有索引,上面查詢,第一個走索引,第二個不走索引,第二個使用了函數之后,name 所在的索引樹是無法快速定位需要查找的數據所在的頁的,只能將所有頁的記錄加載到內存中,然后對每條數據使用函數進行計算之后再進行條件判斷,此時索引無效了,變成了全表數據掃描。
結論:索引字段使用函數查詢使索引無效。
運算符使索引無效
mysql select * from test1 a where id = 2 - 1; +----+-----------+-----+-------------------+ | id | name | sex | email | +----+-----------+-----+-------------------+ | 1 | javacode1 | 1 | javacode1@163.com | +----+-----------+-----+-------------------+ 1 row in set (0.00 sec) mysql select * from test1 a where id+1 = 2; +----+-----------+-----+-------------------+ | id | name | sex | email | +----+-----------+-----+-------------------+ | 1 | javacode1 | 1 | javacode1@163.com | +----+-----------+-----+-------------------+ 1 row in set (2.41 sec)
id 上有主鍵索引,上面查詢,第一個走索引,第二個不走索引,第二個使用運算符,id 所在的索引樹是無法快速定位需要查找的數據所在的頁的,只能將所有頁的記錄加載到內存中,然后對每條數據的 id 進行計算之后再判斷是否等于 1,此時索引無效了,變成了全表數據掃描。
結論:索引字段使用了函數將使索引無效。
使用索引優化排序
我們有個訂單表 t_order(id,user_id,addtime,price),經常會查詢某個用戶的訂單,并且按照 addtime 升序排序,應該怎么創建索引呢?我們來分析一下。
在 user_id 上創建索引,我們分析一下這種情況,數據檢索的過程:
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
走 user_id 索引,找到記錄的的 id
通過 id 在主鍵索引中回表檢索出整條數據
重復上面的操作,獲取所有目標記錄
在內存中對目標記錄按照 addtime 進行排序
我們要知道當數據量非常大的時候,排序還是比較慢的,可能會用到磁盤中的文件,有沒有一種方式,查詢出來的數據剛好是排好序的。
我們再回顧一下 mysql 中 b + 樹數據的結構,記錄是按照索引的值排序組成的鏈表,如果將 user_id 和 addtime 放在一起組成聯合索引(user_id,addtime),這樣通過 user_id 檢索出來的數據自然就是按照 addtime 排好序的,這樣直接少了一步排序操作,效率更好,如果需 addtime 降序,只需要將結果翻轉一下就可以了。
總結一下使用索引的一些建議
鴻蒙官方戰略合作共建——HarmonyOS 技術社區
在區分度高的字段上面建立索引可以有效的使用索引,區分度太低,無法有效的利用索引,可能需要掃描所有數據頁,此時和不使用索引差不多
聯合索引注意最左匹配原則:必須按照從左到右的順序匹配,mysql 會一直向右匹配直到遇到范圍查詢 (、、between、like) 就停止匹配,比如 a = 1 and b = 2 and c 3 and d = 4 如果建立 (a,b,c,d) 順序的索引,d 是用不到索引的,如果建立 (a,b,d,c) 的索引則都可以用到,a,b,d 的順序可以任意調整
3. 查詢記錄的時候,少使用 *,盡量去利用索引覆蓋,可以減少回表操作,提升效率
4. 有些查詢可以采用聯合索引,進而使用到索引下推(IPC),也可以減少回表操作,提升效率
5. 禁止對索引字段使用函數、運算符操作,會使索引失效
6. 字符串字段和數字比較的時候會使索引無效
7. 模糊查詢 % 值 % 會使索引無效,變為全表掃描,但是 值 % 這種可以有效利用索引
8. 排序中盡量使用到索引字段,這樣可以減少排序,提升查詢效率
關于 MySQL 中如何使用索引就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
向 AI 問一下細節