從數據庫的執行計劃來談談SQL索引優化
本文分4個章節:1.分析SQL語句的執行流程。2.分析explain執行計劃中每個列的含義。3.針對explain列的含義來進行索引優化,4.優化總結
文章目錄
- 從數據庫的執行計劃來談談SQL索引優化
- 1.SQL語句的執行流程
- 2.執行計劃解析
- 1. id 列
- 2. select_type 列
- 3. table 列
- 4. type 列
- 5. possible_keys 列
- 6. key 列
- 7. key_len 列
- 8. ref 列
- 9. rows 列
- 10. filtered 列
- 11. extra 列
- 3. 索引優化
- 1. 全值匹配
- 2. 最佳左前綴法則
- 3. 避免在索引列上做計算操作
- 4. 範圍條件右邊的列無法使用索引
- 5. 儘量使用覆蓋索引
- 6. 範圍條件查找能夠命中索引
- 7. IS NOT NULL 無法使用索引
- 8. 模糊查詢以通配符開頭索引失效
- 9. 字符串類型不加單引號索引失效
- 10. or使用多數情況下索引會失效‘
- 11. 負向查詢條件不能使用索引
- 12. 排序對索引的影響
- 13. 局部索引的使用
- 4. 索引優化總結
1.SQL語句的執行流程
首先了解一下MySQL的邏輯架構圖:
通過圖片可以大致將MySQL架構分爲兩個層來實現:Server層和存儲引擎。
連接器:
執行SQL語句的第一步肯定是需要連接數據庫。這部分負責用戶的身份認證。
查詢緩存(8.0移除):
該部分緩存了執行過的SELECT語句以及相應的結果集,並以key-value的方式緩存在內存中。
建立連接後,執行查詢語句的時候首先會在這裏驗證sql語句是否執行過,如果命中了key,就直接會將value返回給客戶端,如果沒有就進入分析器中執行後續操作。
但是實際業務中會頻繁發生緩存失效的問題,因爲當你執行對錶執行一個更新操作後,這個表上的所有查詢緩存都會被清空,所以一般不推薦使用查詢緩存。
在MySQL8.0版本中改功能被移除。
分析器:
該部分主要是分析SQL語句的作用是什麼,大致分爲以下兩步:
- 詞法分析:一條SQL語句有多個字符串組成,首先要提取關鍵字,select、insert、表名、字段名、查詢條件等等。
- 語法分析校驗sql是否符合MySQL的語法。
當分析完SQL語句之後,接着就需要對該語句進行優化。
優化器:
優化器的作用就是選擇較優的執行方案來執行SQL語句,多表查詢的關聯順序、是選擇索引還是全表查詢。
當經過優化器之後,該SQL語句具體如何執行基本就定下來了。
執行器:
執行SQL語句。
2.執行計劃解析
通過explain來查看sql語句的執行計劃,從而提升SQL查詢語句的性能。
字段名 | 用途 |
---|---|
id | 每個select關鍵字都對應一個id |
select_type | select關鍵字對應的查詢類型 |
table | 表名 |
partitions | 匹配的分區信息 |
type | 單表的訪問方法 |
possible_keys | 可能用到的索引 |
key | 實際使用的索引 |
key_len | 實際使用到的索引長度 |
ref | 當使用索引列等值查詢時,與索引列進行等值匹配的對象信息 |
rows | 預計需要讀取的記錄條數 |
filtered | 某個表經過條件過濾後剩餘的記錄條數百分比 |
Extra | 一些額外的信息 |
建表:
DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user (`id`, `name`, `update_time`)
VALUES (1,'a','2017-12-22 15:27:18'), (2,'b','2017-12-22 15:27:18'), (3,'c','2017-12-22 15:27:18');
DROP TABLE IF EXISTS `gro`;
CREATE TABLE `gro` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `gro` (`id`, `name`) VALUES (1,'group1'),(2,'group2'),(3,'group3');
DROP TABLE IF EXISTS user_group;
CREATE TABLE `user_group` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_group_id` (`group_id`),
KEY `idx_user_group_id` (`user_id`,`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user_group (`id`, `user_id`, `group_id`, `remark`)
VALUES (1,1,1,'bak1'), (2,2,2,'bak2'), (3,3,3,'bak3');
1. id 列
該列的值表示的是select的序列號,id 的順序是按 SELECT 出現的順序增長的。id列越大執行優先級越高,id 相同則從上往下執行,id 爲 NULL 最後執行。
MySQL將 SELECT
查詢分爲簡單查詢 SIMPLE
和複雜查詢 PRIMARY
。
複雜查詢包括:簡單子查詢、派生表( FROM
語句中的子查詢)、UNION
和 UNION ALL
查詢。
簡單查詢:
複雜查詢:
-
簡單子查詢:
注意:在查詢過程中最好將子查詢轉換爲連接查詢,子查詢會在查詢過程中創建臨時表,因此存在一個創建銷燬表的過程,不過優化器一般會將子查詢優化爲連接查詢。
沒有優化:
select_type=subquery
就表示子查詢中的第一個查詢。優化:
優化器將子查詢轉化爲連接查詢,查詢計劃中兩個id值是相同的:
-
派生表:
該查詢首先會遍歷gro表索引樹,因爲只查詢了主鍵列故不需要查詢表(type=index)。接着會對派生表進行全表掃描。select_type=derived
表示:派生表查詢,既from字句中的子查詢。 -
union和union all查詢:
UNION
結果總是放在一個匿名臨時表中,id爲null最後執行。跟 UNION
對比,UNION ALL
無需爲最終結果而去重,僅是單純的將多個查詢結果集中的記錄合併成一個並返回給用戶,所以不會使用到臨時表,故沒有 id
爲 NULL
記錄。如下所示:
2. select_type 列
每一個 SELECT
關鍵字的查詢都定義了一個 select_type
屬性,知道這個查詢屬性就能知道在整個查詢語句中所扮演的角色。
1)SIMPLE
:簡單查詢。查詢不包含子查詢 和 UNION
。
2)PRIMARY
:複雜查詢中最外層的SELECT
,可參照上面的 UNION
查詢語句。
3)SUBQUERY
:包含的子查詢語句無法轉換爲 semi-join
,並且爲不相關子查詢,查詢優化器採用物化方案執行該子查詢,該子查詢的第一個 SELECT
就會 SUBQUERY
。該查詢由於被物化,只需要執行一次
。如上面簡單子查詢中沒有優化的查詢。
4)DERIVED
:對於採用物化形式執行的包含派生表的查詢,該派生表的對應的子查詢爲 DERIVED
。具體可以看上面的派生表例子。
5)UNION
:在 UNION
查詢語句中的第二個和緊隨其後的 SELECT
。
6)UNION RESULT
:MySQL選擇使用臨時表完成 UNION
查詢的去重工作。
當 select_type
爲這個值時,經常可以看到table的值是 <unionN,M>
,這說明匹配的 id 行 是這個集合的一部分。請看上面 UNION
查詢示例。
這些是比較常見的值,還有MATERIALIZED
、DEPENDENT SUBQUERY
…
3. table 列
這個值可能是表名、表的別名或者一個未查詢產生臨時表的標識符,如派生表、子查詢或集合。
當 FROM
子句中有子查詢時,如果優化器採用的物化方式,table 列是 <derivenN>
格式,表示當前查詢依賴 id=N
的查詢,於是先執行 id=N
的查詢。如上面的派生表查詢。
當使用 UNION
查詢時,UNION RESULT
的 table 列的值爲 <UNION1,2>
,1和2表示參與 UNION
的 SELECT 的行 id。如上面的union查詢。
4. type 列
這一列表示關聯類型或訪問類型,即MySQL決定如何查找表中的行,查找數據行記錄的大概範圍。依次從最優到最差分別爲:null>system > const > eq_ref > ref > range > index > ALL 一般來說,得保證查詢達到range級別,最好達到ref。
NULL:mysql能夠在優化階段分解查詢語句,在執行階段用不着再訪問表或索引。例如:在索引列中選取最小值,可以單獨查找索引來完成,不需要在執行時訪問表 mysql> explain select min(id) from user。
-
system/const
:表示使用主鍵索引或者唯一非空索引來作爲查詢條件並且匹配的是一個常量,因此該查詢結果最多隻有一行。system
是const的一種特例,出現這種情況表示表中只有一條記錄。 -
eq_ref
:在連接查詢時如果被驅動表是通過主鍵或者唯一二級索引等值匹配的方式進行訪問,則對被驅動表訪問方式就是eq_ref
。 如下所示:gro.id是主鍵。
-
ref
:相比較於eq_ref是使用唯一索引,ref使用的是普通索引或者唯一性索引的部分前綴,索引要和某個值比較,可能會有多個符合條件的記錄。a. 簡單查詢,使用普通索引gro.name。
b. 關聯查詢,使用了user_group的聯合索引idx_user_group_id的最左邊前綴的user_id。
4.range
:使用索引獲取範圍區間的記錄,通常出現在in,between,<,>,>=
等操作中。
-
index
:掃描全表索引(index
是從索引中讀取的,而ALL
是從硬盤中讀取)
-
ALL
:全表掃描,MySQL 需要從頭到尾去查找表中所需要的行。通常情況下這需要增加索引來進行優化了。
5. possible_keys 列
possible_keys
列表示查詢可能使用哪些索引來查找。
EXPLAIN
執行計劃結果可能出現 possible_keys
列,而 key
顯示 NULL
的情況,這種情況是因爲表中數據不多,MySQL 會認爲索引對此查詢幫助不大,選擇了全表查詢。
如果 possible_keys
列爲 NULL
,則沒有相關的索引。在這種情況下,可以通過檢查 WHERE
子句去分析下,看看是否可以創造一個適當的索引來提高查詢性能,然後用 EXPLAIN
查看效果。
另外注意:不是這一列的值越多越好,使用索引過多,查詢優化器計算時查詢成本高,所以如果可能的話,儘量刪除那些不用的索引。
6. key 列
key
列表示實際採用哪個索引來優化對該表的訪問。
如果沒有使用索引,則該列是 NULL。如果想強制 MySQL使用或忽視 possible_keys
列中的索引,在查詢中使用 force index
、ignore index
。
7. key_len 列
key_len
列表示當查詢優化器決定使用某一個索引查詢時,該索引記錄的最大長度。
計算規則如下:
-
字符串
char(n):n字節長度
varchar(n):2字節存儲字符串長度,如果是utf-8,則長度 3n + 2
**注意:**該索引列可以存儲
NULL
值,則key_len
比不可以存儲NULL
值時多1個字節。比如:varchar(50),則實際佔用的
key_len
長度是 3 * 50 + 2 = 152,如果該列允許存儲NULL
,則key_len
長度是153。 -
數值類型
tinyint:1字節 smallint:2字節 int:4字節 bigint:8字節
-
時間類型
date:3字節 timestamp:4字節 datetime:8字節
索引最大長度是768字節,當字符串過長時,MySQL 會做一個類似左前綴索引的處理,將前半部分的字符提取出來做索引。
例1:
user_group
表中的聯合索引 idx_user_group_id
由 user_id
和 group_id
兩個int 列組成,並且每個 int 是 4 字節。
例2:
再看 user
表 name 字段是 varchar(45) 變長字符串類型,key_len
爲138 等於 45 * 3 + 2 (變長字節) + 1字節(允許存儲NULL值)
8. ref 列
ref
列顯示了在 key
列記錄的索引中,表查找值所用到的列或常量,常見的有:const
(常量),字段名
(例:user.id
)。
9. rows 列
ref
列顯示了在 key
列記錄的索引中,表查找值所用到的列或常量,常見的有:const
(常量),字段名
(例:user.id
)。
10. filtered 列
對於單表來說意義不大,主要用於連接查詢中。
前文中也已提到 filtered
列,是一個百分比的值,對於連接查詢來說,主要看驅動表
的 filtered
列的值 ,通過 rows * filtered/100
計算可以估算出被驅動表
還需要執行的查詢次數。
11. extra 列
Extra
列提供了一些額外信息。這一列在 MySQL中提供的信息有幾十個,這裏僅列舉一些常見的重要值如下:
-
Using index
:查詢的列被索引覆蓋,並且WHERE
篩選條件是索引的前導列,使用了索引性能高。一般是使用了覆蓋索引(查詢列都是索引列字段)。對於 INNODB 存儲引擎來說,如果是輔助索引(非聚集索引)性能會有不少提高,並且也不需要回表查詢。
-
Using where;Using where
:查詢的列被索引覆蓋,並且WHERE
篩選條件是索引列之一,但並不是索引的前導列,意味着無法直接通過索引查找來查詢到符合條件的數據。還有一種情況是前導列但是條件爲範圍查詢。首先需要把user_group中的group_id這個索引刪除,如果沒有刪除那麼
key=idx_group_id Extra=Using index
,因爲檢索條件是前導列。
沒有刪除
-
NULL
:查詢的列未被索引覆蓋,並且WHERE
篩選條件是用到了索引,但是部分字段未被索引覆蓋,不是純粹地用到了索引,也不是完全沒用到索引。或者沒有使用到額外條件來查詢(查詢的列沒有完全覆蓋索引)。
-
Using temporary
:MySQL中需要創建一張內部臨時表來處理查詢,一般出現這種情況就需要考慮優化了,首先是想到用索引來優化。
通常在許多執行包括DISTINCT、GROUP BY、ORDER BY等子句查詢過程中,如果不能有效利用索引來完成查詢,MySQL很有可能會尋求建立內部臨時表來執行查詢。
所以,執行計劃中出現了
Using temporary
並不是個好兆頭,因爲建立與維護臨時表要付出很大的成本的,要考慮使用索引
來優化改進。
-
Using filesort
:MySQL 會對結果使用一個外部索引排序,而不是按索引次序從表裏讀取行。此時 MySQL 會根據聯接類型瀏覽所有符合條件的記錄,並保存排序關鍵字和行指針,然後排序關鍵字並按順序檢索行信息。這種情況下一般也是要考慮使用索引來優化的。查詢中需要使用
filesort
的方式進行排序的記錄非常多,那麼這個過長是很耗時的,想辦法將使用文件排序
的執行方式改進爲使用索引
進行排序。 -
Index merges
:通常顯示爲Using sort_union(...)
說明準備用Sort-Union
索引合併方式來查詢;顯示爲Using union(...)
,說明準備用Union
索引合併方式查詢;顯示爲Using intersect(...)
,說明準備使用Intersect
索引合併方式查詢。
3. 索引優化
# 重建 `staff` 表
DROP TABLE `staff`;
CREATE TABLE `staff` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`s_name` VARCHAR(24) NOT NULL DEFAULT '' COMMENT '花名',
`s_no` INT(4) NOT NULL DEFAULT 0 COMMENT '工號',
`work_age` int(11) NOT NULL DEFAULT '0' COMMENT '工齡',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',
`arrival_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '備註', # 允許 NULL
PRIMARY KEY (`id`), # 主鍵
UNIQUE KEY idx_s_name (s_name), # 唯一索引
KEY idx_s_no (s_no), # 普通索引
KEY `idx_name_age_position` (`name`,`work_age`,`position`) USING BTREE # 聯合索引
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';
# 初始化 `staff` 表數據
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('zhangsan','zs',10,2,'manager',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('lisi','ls',11,3,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('wangwu','ww',12,8,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('zhangliu','zl',110,5,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('xiaosun','xs',111,5,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('donggua','dg',200,3,'dev',NOW());
創建一個staff表來實踐。
其實中心思想就是:儘量通過索引來查數據,並且要覆蓋索引,避免進行回表操作(需要在兩次b+tree中取數據),並且避免過多的創建索引,存儲插入數據的時候需要更多的時間去維護。儘量在兩者中取得平衡。
1. 全值匹配
按索引順序匹配使用。
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’;
type爲ref,使用到了索引,效率高。
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’ AND work_age = 2;
按照聯合索引的順序來查,type=ref使用了索引。
2. 最佳左前綴法則
如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
EXPLAIN SELECT * FROM staff WHERE name = ‘zhangsan’ AND work_age = 3 AND position = ‘manager’;
EXPLAIN SELECT * FROM staff where position = ‘dev’ AND name = ‘zhangsan’ AND work_age = 2;
這條語句並沒有按照索引順序來查詢,但是mysql優化器會自動優化,所以還是會正確的使用到索引。
以下執行都是全表掃描,type爲ALL
,不符合最左前綴法則:
EXPLAIN SELECT * FROM staff WHERE work_age = 2 AND position =‘dev’;
3. 避免在索引列上做計算操作
索引上儘量避免做函數計算等操作,會導致索引失效而轉向全表掃描。
WHERE
條件後面索引列使用函數:
4. 範圍條件右邊的列無法使用索引
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’ AND work_age > 2 AND position =‘dev’;
可以看到type=range
,並且key_len=78 (3*24+2+4)
只使用了name和work_age索引, 而 position 字段並沒有用到索引(沒有使用到BTree的索引去查詢),只是從 name = 'zhangsan' AND work_age > 2
條件返回的結果集中,再過濾符合 position 字段條件的數據。
5. 儘量使用覆蓋索引
覆蓋索引:簡單理解,只訪問建了索引的列。減少使用 SELECT *
語句查詢列。
使用了覆蓋索引:
EXPLAIN SELECT name,work_age FROM staff WHERE name= ‘zhangsan’ AND work_age = 3;
可以看到Extra=Using index
,但是這種情況在生產環境中一般無法滿足要求。
附一個曾經線上SQL的優化記錄:
artist 表有幾十萬條的數據量,第一條執行的SQL沒有索引直接查詢,查詢耗時 0.557
毫秒;第一次優化
新建 founded 字段作爲普通索引,查詢耗時 0.0224
毫秒;第二次優化
再次重建聯合索引 founded_name,優化後查詢耗時:0.0051
毫秒。因爲使用了覆蓋索引查詢方式,基於此優化,SQL查詢效率提升非常明顯。
6. 範圍條件查找能夠命中索引
範圍條件查詢首先優化器會判斷預估記錄數和全表記錄數的差距,如果差距不大那麼即使where條件是通過索引來查詢的,優化器可能會直接全表查詢。
範圍條件主要包括 <、<=、>、>=、between
等。
若條件中範圍列有普通索引和主鍵索引同時存在, 優先使用主鍵索引:
EXPLAIN SELECT * FROM staff WHERE staff.s_no > 10 AND staff.id > 2;
key=primary
代表使用的索引爲主鍵索引。
範圍列可以用到索引,注意聯合索引必須符合最左前綴法則,如果查詢條件中有兩個範圍列則無法全用到索引,優化器會去選擇:
explain select * from staff where staff.name != ‘zl’ AND s_no <12;
explain select * from staff where staff.name != ‘zl’ AND s_no >1;
該條語句直接type=ALL
是因爲rows=6
和表中總記錄數相同,優化器直接使用全表查詢了。
若條件中範圍查詢和等值查詢同時存在,優先匹配等值查詢列的索引:
EXPLAIN SELECT * FROM staff WHERE staff.s_no > 10 AND staff.s_name = ‘zl’;、
7. IS NOT NULL 無法使用索引
索引列建議都使用 NOT NULL 約束
及默認值,單列索引不存 NULL 值,聯合索引不存全部爲 NULL 的值,如果列允許爲 NULL,查詢結果可能不符合預期。
staff 表中爲 remark
字段新建普通索引:
ALTER TABLE staff ADD INDEX idx_remark (remark);
IS NULL
查詢命中索引:
EXPLAIN SELECT * FROM staff WHERE staff.remark IS NULL;
給remark添加完索引後需要賦值,因爲remark默認是null,這條語句會命中所有行,優化器就會進行全表查詢。上圖是賦值過後的結果。
IS NOT NULL
查詢不會命中索引:
EXPLAIN SELECT * FROM staff WHERE staff.remark IS NOT NULL;
8. 模糊查詢以通配符開頭索引失效
like '%xx'
或 like '%xx%'
前導模糊查詢不能命中索引:
EXPLAIN SELECT * from staff where name like ‘%zhang%’;
如何使用模擬查詢才能命中索引?
-
like 'xx%'
**非前導模糊查詢可以命中索引: **
EXPLAIN SELECT * FROM staff WHERE name LIKE ‘zhang%’;
-
使用覆蓋索引,查詢字段必須要建立覆蓋索引字段
EXPLAIN SELECT name,work_age FROM staff WHERE name LIKE ‘%zhang%’;
聯合索引是
idx_name_work_age_position
9. 字符串類型不加單引號索引失效
字符串的數據類型一定要將常量值使用單引號,這個在日常開發中要特別注意的,數據類型出現隱式轉換的時候不會命中索引。
不加單引號索引失效
EXPLAIN SELECT * FROM staff WHERE name = 1;
name=1 類似於在該字段上做了一個函數運算,因此不會走索引的。
加單引號會命中索引:
EXPLAIN SELECT * FROM staff WHERE name = ‘zhangsan’;
10. or使用多數情況下索引會失效‘
EXPLAIN SELECT * FROM staff WHERE name=‘zhangsan’ OR work_age = 2;
儘管name和work_age是聯合索引,但是work_ag列上並沒有索引,因此會走全表掃描,存在全表掃描的情況下,就沒必要在進行一次索引掃描。
OR 後面也使用索引列:
explain select * from staff where s_name=‘zs’ or s_no =10;
這裏需要在staff表中插入新的數據,不然優化器可能會優化成全表搜索。
Using union
:表示使用or連接各個使用索引的條件時,該信息表示從處理結果獲取並集。
如果執行計劃 Extra
列出現了 Using sort_union(...)
的提示,說明準備使用 Sort-Union
索引合併的方式執行查詢。如果出現了 Using intersect(...)
的提示,說明準備使用 Intersect
索引合併方式執行查詢,如果出現了 Using union(...)
的提示 ,說明準備使用 Union
索引合併方式執行查詢。括號中 ...
表示需要進行索引合併的索引名稱
**使用UNION優化改進: **
explain select * from staff where s_name=‘zs’ union select * from staff where s_no =10;
使用 UNION
執行計劃中出現了第三條記錄,Extra
中出現 Using temporary
,說明 MySQL因爲不能有效利用索引,建立了內部臨時表來執行查詢。當你在使用 DISTINCT 、GROUP BY、UNION
等子句中的查詢過程中,都有可能會出現該擴展信息。
使用UNION ALL進一步優化:
explain select * from staff where s_name=‘zs’ union all select * from staff where s_no =10;
執行結果中不再出現內部臨時表,具體用的時候結合實際需求來定是否使用。
11. 負向查詢條件不能使用索引
負向查詢條件包括:!=、<>、NOT IN、NOT EXISTS、NOT LIKE
等。
不會命中索引:
EXPLAIN SELECT * FROM staff WHERE s_no !=1 AND s_no != 2;
EXPLAIN SELECT * FROM staff WHERE s_no NOT IN (1,2);
使用IN優化,命中索引:
EXPLAIN SELECT * FROM staff WHERE s_no IN (11,12);
但是使用 IN
命中索引有個前提,是查詢條件字段數據區分度要高,通常如:狀態、類型、性別之類的字段。
12. 排序對索引的影響
ORDER BY
是經常用的語句,排序也遵循最左前綴列的原則。
查詢所有列未命中索引:
EXPLAIN SELECT * FROM staff ORDER BY name,work_age;
覆蓋索引查詢可命中索引: 但要遵循最左前綴法則
可以看到一開始沒有遵循最左前綴法則出現了Using filesort
,一看到這個我們就需要考慮對SQL語句進行優化了。
13. 局部索引的使用
局部索引,區別於最左列索引(順序取索引中靠左的列的查詢),它只取某列的一部分作爲索引。
INNODB存儲引擎下,一般是字符串類型,很長,全部作爲索引大大增加存儲空間,索引也需要維護,對於長字符串,又想作爲索引列,可取的辦法就是取前一部分(局部),代表一整列作爲索引串。
如何確保這個前綴能代表或大致代表這一列?MySQL中有個概念是 索引選擇性
,是指索引中不重複的值的數目(也稱基數X)與整個表該列記錄總數(T)的比值。基數可以通過SHOW INDEX FROM 表名
查看。
比如一個列表 [1,2,2,3,5,6],總數是 6,不重複值數目爲 5,選擇性爲 5/6,因此選擇性範圍是[X/T, 1],這個值越大,表示列中不重複值越多,越適合作爲局部索引,而唯一索引(UNIQUE KEY)的選擇性是1。
EXPLAIN SELECT * FROM staff where remark LIKE ‘xxx%’;
可以看到key_len的值非常大,此時對 remark 字段重建局部索引:
ALTER TABLE staff DROP INDEX idx_remark_part, ADD INDEX idx_remark_part(remark(5));
再次查詢:key_len變成了18。
4. 索引優化總結
上面羅列出了一些使用索引過程中的優化,但是索引不宜濫用創建。下面總結出不宜創建索引的情況以及一些創建索引的建議:
-
更新非常頻繁字段不宜建索引:
因爲字段更新臺頻繁,會導致B+樹的頻繁的變更,重建索引。所以這個過程是十分消耗數據庫性能的。
-
區分度不大的字段不宜建索引:
比如類似性別這類的字段,區分度不大,建立索引的意義不大。因爲不能有效過濾數據,性能和全表掃描相當。另外注意一點,返回數據的比例在
30%
之外的,優化器不會選擇使用索引。 -
業務中有唯一特性的字段,建議建成
唯一索引
業務中如果有唯一特性的字段,即使是多個字段的組合,也儘量都建成唯一索引。儘管唯一索引會影響插入效率,但是對於查詢的速度提升是非常明顯的。此外,還能夠提供校驗機制,如果沒有唯一索引,高併發場景下,可能還會產生髒數據。
-
多表關聯時,要確保關聯字段上必須有索引
-
最佳索引實踐口訣
全值匹配我最愛,最左前綴要遵守;
帶頭大哥不能死,中間兄弟不能斷;
索引列上少計算,範圍之後全失效;
Like百分寫最右,覆蓋索引不寫星;
不等空值還有or,索引失效要少用;
VAR引號不可丟,SQL高級也不難!
-
EXPLAIN
執行計劃實踐總結如果還是覺得
EXPLAIN
執行計劃列太多了,也記不住呀,那麼請重點關注以下幾列:第1列
:ID越大,執行的優先級越高;ID相等,從上往下優先順序執行。第2列
:select_type 查詢語句的類型,SIMPLE簡單查詢,PRIMARY複雜查詢,DERIVED衍生查詢(from子查詢的臨時表),派生表。第4列
:請重點掌握,type類型,查詢效率優先級:system->const->eq_ref->ref->range->index->ALLALL
是最差
的,system
是最好
的,性能最佳,阿里巴巴開發規約中要求最差也得到range
級別,而不能有index、ALL
。
文章大部分開源於公衆號:Java愛好者社區 ,作者東昇的思考。 但是原文章有些內容測試結果不太一樣不知道是不是版本問題,本文使用的是myql5.7。
引,高併發場景下,可能還會產生髒數據。
-
多表關聯時,要確保關聯字段上必須有索引
-
最佳索引實踐口訣
全值匹配我最愛,最左前綴要遵守;
帶頭大哥不能死,中間兄弟不能斷;
索引列上少計算,範圍之後全失效;
Like百分寫最右,覆蓋索引不寫星;
不等空值還有or,索引失效要少用;
VAR引號不可丟,SQL高級也不難!
-
EXPLAIN
執行計劃實踐總結如果還是覺得
EXPLAIN
執行計劃列太多了,也記不住呀,那麼請重點關注以下幾列:第1列
:ID越大,執行的優先級越高;ID相等,從上往下優先順序執行。第2列
:select_type 查詢語句的類型,SIMPLE簡單查詢,PRIMARY複雜查詢,DERIVED衍生查詢(from子查詢的臨時表),派生表。第4列
:請重點掌握,type類型,查詢效率優先級:system->const->eq_ref->ref->range->index->ALLALL
是最差
的,system
是最好
的,性能最佳,阿里巴巴開發規約中要求最差也得到range
級別,而不能有index、ALL
。
文章大部分開源於公衆號:Java愛好者社區 ,作者東昇的思考。 但是原文章有些內容測試結果不太一樣不知道是不是版本問題,本文使用的是myql5.7。