由於我在最近的項目中對mysql的某張表的某個varchar列加上前綴索引后,這張表主鍵為id,其他列沒加索引,在查詢語句中即使where子句里只有course_num like "4%"
這個條件,SELECT * FROM test WHERE course_num LIKE "4%"
,通過使用explain發現還是會走all類型進行全表查詢。隨后,我發現用絕大多數的博文中的數據進行復盤測試時,得到的結果和他們的對不上,在翻閱MySQL的查詢優化器相關知識時,絕大多數博文中寫的形如"4%"
這種百分號在后面的一定會走range類型用前綴索引進行查詢,這不是絕對正確的,在一些情況下不會走前綴索引查詢,可能這些博文默認讀者已經知曉了查詢優化器的某些前置知識。然而實際上需要結合查詢優化器基於成本計算去選擇是否使用索引。這里就不再贅述索引選擇性。好了,廢話不多說,看下面的測試用例的演示。
首先建立數據表和前綴索引(在本例中由於varchar列長度太短所以對整列建立前綴索引),其中本例用到的前綴索引為index_uname
。
CREATE TABLE test(
id INT NOT NULL AUTO_INCREMENT,
major_abbr VARCHAR(5) NOT NULL,
course_num VARCHAR(5) NOT NULL,
PRIMARY KEY(id),
KEY index_course_num(course_num)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
然后插入10條數據:
INSERT INTO test VALUES (NULL,"CS","2000"),(NULL,"CS","2100"),
(NULL,"CS","3000"),(NULL,"CS","3100"),(NULL,"CS","3500"),(NULL,"CS","3600"),
(NULL,"CS","4000"),(NULL,"CS","4100"),(NULL,"CS","4200"),(NULL,"CS","4500");
先獲取全部數據:
select * from test;
id major_abbr course_num
1 CS 2000
2 CS 2100
3 CS 3000
4 CS 3100
5 CS 3500
6 CS 3600
7 CS 4000
8 CS 4100
9 CS 4200
10 CS 4500
查詢test表中course_num列前綴為4的所有行。
-
計算返回的行數占表的總行數的比例:
SELECT (SELECT COUNT(*) FROM test WHERE course_num LIKE "4%")/COUNT(*) AS proportion FROM test;
proportion 0.4000
再用explain分析:
EXPLAIN SELECT * FROM test WHERE course_num LIKE "4%";
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test ALL index_course_num \N \N \N 10 Using where
看見ALL,發現走的全表查詢,此時的返回行數占總行數的40%。
-
修改test表的最后一行的course_num列為'3500':
UPDATE test SET course_num='3500' WHERE id=10;
這時再獲取test表全部數據看一下:
先獲取全部數據:
select * from test;
id major_abbr course_num 1 CS 2000 2 CS 2100 3 CS 3000 4 CS 3100 5 CS 3500 6 CS 3600 7 CS 4000 8 CS 4100 9 CS 4200 10 CS 3500
計算返回的行數占表的總行數的比例:
SELECT (SELECT COUNT(*) FROM test WHERE course_num LIKE "4%")/COUNT(*) AS proportion FROM test;
proportion 0.3000
再用explain分析:
EXPLAIN SELECT * FROM test WHERE course_num LIKE "4%";
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test range index_course_num index_course_num 22 \N 3 Using index condition
看見range,發現走的index_course_num索引查詢,此時的返回行數占總行數的30%。
對於like后跟形如"4%"
百分號放在尾部的查詢條件的結論:這種百分號放在尾部的查詢條件在實際執行中不一定會去使用前綴索引,因為在mysql查詢優化器中基於成本計算是否使用索引的代價有這樣一種方式--我們在用前綴索引也就是輔助索引進行模糊匹配時是有序的,輔助索引也是用b+tree存放的,在該b+tree的葉子節點存放的對應的主鍵值一般情況下不是有序的,這樣可以認為取的每一行記錄都需要讀取磁盤一次(表的數據在磁盤中划分為塊的形式,在內存中划分為頁的形式),記test表在磁盤上所占的塊數為\(B\),執行上述查詢語句返回的行數為\(T\),當T足夠大的時候也就是大致的\(T>B\),這時候走全表查詢會比走輔助索引到主索引查詢更優。當然,如果\(T\)足夠小的時候,走索引查詢更優。這個\(T\)和\(B\)的關系通常用放回的行數占總行數的比例去衡量,目前來說沒有一個固定的臨界值,一般的經驗值為30%,所以這就是為什么在上述第一種情況下會走全表查詢,而在上述第二種情況下會走前綴索引查詢。在實際情況中是否使用索引還要根據這個比例(COUNT(*)
等等除外),當然MySQL查詢優化器最終選擇的索引實際上也不一定是最優的,由此其他文章中寫的形如百分號放后面的like匹配一定會使用前綴索引是不夠robust。這樣的話我們在分析sql語句的效率時,可以加上FORCE INDEX (index_name)
強制使用index_name
索引,配合explain去分析查詢返回的行數占總行數的比例,這種也可以用於找到合適的前綴長度。
如果覺得我解釋的某些地方有歧義的,歡迎指出。