第一節:MySQL索引簡介、執行計划剖析、最佳實踐入門


一. 索引簡介

1. 什么是索引

 索引是排序好的快速查找的數據結構,用來幫助MySQL高效獲取需要的數據結果。索引會影響到MySQL查找(WHERE的查詢條件)和排序(ORDER BY)兩大功能!理解索引工作的方式最好的辦法就是把索引比喻成書的目錄,當需要查看特定的章節時通過查看目錄的方式往往要比查看整個書的內容要有效很多。

補充:

 (1). 除了數據本身之外,數據庫還維護着一個滿足特定查找算法的數據結構,這些數據結構以某種方式指向數據,這樣就可以在這些數據結構的基礎上實現高級查找算法,這種數據結構就是索引。

 (2). 一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲在磁盤上。

2. 索引的分類

 我們平時所說的索引,如果沒有特別指明,都是指B樹(多路搜索樹,並不一定是二叉的)結構組織的索引。其中聚集索引,次要索引,覆蓋索引,復合索引,前綴索引,唯一索引默認都是使用B+樹索引,統稱索引。當然,除了B+樹這種數據結構的索引之外,還有哈希索引(Hash Index)等。

A. 分類1

 單值索引:一個索引只包含單個列,一個表可以有多個單列索引。

 唯一索引:索引列的值必須唯一,但是允許空值。

 復合索引:一個索引包含多個字段。

B. 分類2

 覆蓋索引:就是select的數據列只用從索引的key中就能夠獲取,不必從數據表中讀取,換句話說查詢列要被所使用的索引覆蓋。

C. 分類3

 聚集索引(也叫主鍵索引):通過主鍵創建的索引叫做主鍵索引,也叫聚集索引。

 輔助索引(非聚集索引):除了主鍵索引以外的其它索引。

3. 索引的優缺點

A. 優點:

查找:類似大學圖書館的書目索引,提高數據檢索的效率,降低數據庫的IO成本。

 (2) 排序:通過索引対數據進行排序,降低數據排序的成本,降低了CPU的消耗。

 (3) 幫助服務器避免排序和臨時表。

 (4) 可以將隨機IO變成順序IO。

B. 缺點:

 (1) 實際上索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄,所以索引列也是要占用空間的。(詳見下一節B+Tree結構)

 (2) 雖然索引大大提高了查詢速度,但是同時會降低表的更新速度,例如對表頻繁的進行 INSERT 、UPDATE 和 DELETE 。因為更新表的時候,MySQL不僅要保存數據,還要保存一下索引文件每次更新添加的索引列的字段,都會調整因為更新所帶來的鍵值變化后的索引信息。

 (3) 索引只是提高效率的一個因素,如果MySQL有大數據量的表,就需要花時間研究建立最優秀的索引。

4. 相關SQL語句

-----索引相關語句
--1. 查詢索引
show index from TableName;
--如:查看article表的索引
show index from article;

--2. 刪除索引
DROP INDEX [indexName] ON TableName;
--如:刪除acticle表上的 idx_article_cv 索引
drop index idx_article_cv on article;

--3. 創建索引
CREATE [UNIQUE] INDEX indexName ON TableName(columnName(length));
--如:在article表上創建category_id的單個索引,名為:idx_article_c
CREATE INDEX idx_article_c ON article(category_id);
--如:在article表上創建category_id,views的聯合索引,名為:idx_article_cv
CREATE INDEX idx_article_cv ON article(category_id,views);

---ALTER相關用法
/* 1、該語句添加一個主鍵,這意味着索引值必須是唯一的,並且不能為NULL */
ALTER TABLE TableName ADD PRIMARY KEY(column_list);

/* 2、該語句創建索引的鍵值必須是唯一的(除了NULL之外,NULL可能會出現多次) */
ALTER TABLE TableName ADD UNIQUE indexName(column_list);

/* 3、該語句創建普通索引,索引值可以出現多次 */
ALTER TABLE TableName ADD INDEX indexName(column_list);
--如:在article表中對views列添加索引,名為idx_article_v
alter table article add index idx_article_v(views);

/* 4、該語句指定了索引為FULLTEXT,用於全文檢索 */
ALTER TABLE TableName ADD FULLTEXT indexName(column_list);

 

二. 執行計划

1. 什么是執行計划?

 SQL的執行計划,使用EXPLAIN關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理SQL語句的,explain語句對select,delete,update,insert,replace語句有效。

補充explain的兩個變種(了解即可):

 (1). explain extended:會在 explain 的基礎上額外提供一些查詢優化的信息。緊隨其后通 過 show warnings 命令可以得到優化后的查詢語句,從而看出優化器優化了什么。額外還有 filtered 列,是一個半分比的值,rows * filtered/100 可以估算出將要和 explain 中前一個表 進行連接的行數(前一個表指 explain 中的id值比當前表id值小的表)。

 (2). explain partitions:相比 explain 多了個 partitions 字段,如果查詢是基於分區表的話,會顯示查詢將訪問的分區。

數據准備:

--數據准備
use IndexTestDB;
--1. actor表
DROP TABLE IF EXISTS `actor`;
create table `actor`(
    `id` int(11) NOT NULL,
    `name` varchar(45) DEFAULT NULL,
    `update_time` datetime DEFAULT NULL,
    PRIMARY KEY (`id`)
)  ENGINE= INNODB DEFAULT CHARSET=utf8;
insert into `actor`(`id`,`name`,`update_time`) values(1,'a','2020-12-22 15:27:18'),(2,'b','2020-12-22 15:27:18'),(3,'c','2020-12-22 15:27:18'); 
 
 --2.film表
DROP TABLE IF EXISTS `film`;
create table `film`(
    `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 `film`(`id`,`name`) values(1,'file1'),(2,'file3'),(3,'file3'); 

--3. file_actor表 
DROP TABLE IF EXISTS `film_actor`;
create table `film_actor`(
    `id` int(11) NOT NULL,
    `film_id` int(11)  NOT NULL,
    `actor_id` int(11)  NOT NULL,
    `remark` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_film_actor_id` (`film_id`,`actor_id`)
)  ENGINE= INNODB DEFAULT CHARSET=utf8;
insert into `film_actor`(`id`,`film_id`,`actor_id`) values(1,1,1),(2,1,2),(3,2,1); 
View Code

2. 剖析執行計划

 運行下面語句,發現type為all,即全表掃描。

(1). id

id列的編號是 select 的序列號,有幾個 select 就有幾個id,並且id的順序是按 select 出現的 順序增長的。分三種請情況:

 id 相同,執行順序由上至下。

 id 不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行。

 id 相同和不相同,同時存在。永遠是id大的優先級最高,id相等的時候順序執行。

代碼如下:

set session optimizer_switch='derived_merge=off'; #關閉mysql5.7新特性對衍生表的合並優化
explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;

(2). select_type

數據查詢的類型,主要是用於區別,普通查詢、聯合查詢、子查詢等的復雜查詢。

 A. SIMPLE :簡單的 SELECT 查詢,查詢中不包含子查詢或者 UNION 。

 B. PRIMARY :查詢中如果包含任何復雜的子部分,最外層查詢則被標記為 PRIMARY 。

 C. SUBQUERY :在 SELECT 或者 WHERE 子句中包含了子查詢。

 D. DERIVED :在 FROM 子句中包含的子查詢被標記為 DERIVED(衍生) ,MySQL會遞歸執行這些子查詢,把結果放在臨時表中。

PS: B C D 三個案例詳見開頭id說明那個位置的截圖。

 E. UNION :如果第二個 SELECT 出現在 UNION 之后,則被標記為 UNION ;若 UNION 包含在 FROM子句的子查詢中,外層 SELECT 將被標記為 DERIVED 。

 F. UNION RESULT :從 UNION 表獲取結果的 SELECT 。

(3). table

 這一列表示 explain 的一行正在訪問哪個表。

  當 from 子句中有子查詢時,table列是<derivenN>格式,表示當前查詢依賴 id=N 的查詢,於是先執行 id=N 的查詢。 當有 union 時,UNION RESULT 的 table 列的值為<union1,2>,1和2表示參與 union 的 select 行id。 

(4). type  -重點

訪問類型排列。即MySQL決定如何查找表中的行,查找數據行記錄的大概 范圍。

從最好到最差依次是: system > const > eq_ref > ref > range > index > ALL 。除了 ALL 沒有用到索引,其他級別都用到索引了。

 ① Null:mysql能夠在優化階段分解查詢語句,在執行階段用不着再訪問表或索引。例如:在 索引列中選取最小值,可以單獨查找索引來完成,不需要在執行時訪問表。

 explain select min(id) from film;

 ② system :表只有一行記錄(等於系統表),這是 const 類型的特例,平時不會出現,這個也可以忽略不計。

 ③ const :表示通過索引一次就找到了, const 用於比較 primary key 或者 unique 索引。因為只匹配一行數據,所以很快。如將主鍵置於 where 列表中,MySQL就能將該查詢轉化為一個常量。

set session optimizer_switch='derived_merge=off';
explain select * from (select * from film where id = 1) tmp;

 ④ eq_ref :唯一性索引掃描,讀取本表中和關聯表表中的每行組合成的一行,查出來只有一條錄。除了 system 和 const 類型之外, 這是最好的聯接類型。

explain select * from film_actor left join film on film_actor.film_id = film.id;

 ⑤ ref :非唯一性索引掃描,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前綴,索引要 和某個值相比較,查出來可能有多條記錄

情況1:簡單的select查詢,name為普通索引(非唯一索引)

explain select * from film where name = 'film1';

 

情況2:關聯表查詢,idx_film_actor_id是film_id和actor_id的聯合索引,這里使用到了film_actor 的左邊前綴film_id部分。

explain select film_id from film left join film_actor on film.id = film_actor.film_id;

 ⑥ range :只檢索給定范圍索引行,一般就是在 WHERE 語句中出現了 BETWEEN 、 < > 、 in 等的查詢。這種范圍掃描索引比全表掃描要好,因為它只需要開始於索引樹的某一點,而結束於另一點,不用掃描全部索引。

explain select * from actor where id > 1;

 ⑦ index : Full Index Scan ,全索引掃描, index 和 ALL 的區別為 index 類型只遍歷索引樹。也就是說雖然 ALL 和 index 都是讀全表,但是 index 是從索引中讀的, ALL 是從磁盤中讀取的

下面例子 film表中只有兩個字段,一個是主鍵id,一個是name,name上有索引。

 explain select * from film;

 ⑧ ALL : Full Table Scan ,沒有用到索引,全表掃描。意味着mysql需要從頭到尾去查找所需要的行。

下面的actor表沒有做到全表索引覆蓋,所以下面的的查詢需要全表掃描。

explain select * from actor;

 

PS: 一般來說,得保證查詢至少達到 range 級別,最好達到 ref 。

(5). possible key 和 key

 A. possible_keys :這一列顯示查詢可能使用哪些索引來查找。 explain 時可能出現 possible_keys 有列,而 key 顯示 NULL 的情況,這種情況是因為表中 數據不多,mysql認為索引對此查詢幫助不大,選擇了全表查詢。 如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查 where 子句看是否可 以創造一個適當的索引來提高查詢性能,然后用 explain 查看效果。

 B. key :實際使用的索引。如果為 NULL ,則沒有使用索引。查詢中如果使用了覆蓋索引,則該索引僅僅出現在 key 列表中。如果想強制mysql使用或忽視possible_keys列中的索 引,在查詢中使用 force index、ignore index。

(6). key_len (重點)

 表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。 key_len 顯示的值為索引字段的最大可能長度,並非實際使用長度,即 key_len 是根據表定義計算而得,不是通過表內檢索出的。在不損失精度的情況下,長度越短越好。

計算規則可參考: https://blog.csdn.net/qq_34930488/article/details/102931490

 A. char和varchar

  a. 列長度

  b. 列是否為空:NULL(+1),NOT NULL(+0)

  c. 字符集:utf8mb4=4,utf8=3,gbk=2,latin1=1

  d. 列類型為字符,varchar  +2 , char +0

 char和varchar最終計算公式  key_len = (表字符集長度) * 列長度 +1 (null) + 2(varchar)

 B. 數值類型

  a. tinyint       非空為1,可空為2

  b. smallint    非空為2,可空為3

  c. int            非空為4,可空為5

  d. bigint  非空為8,可空為9

 C. 時間類型

  a. date:非空3字節,可空4字節

     b. timestamp:非空4字節,可空5字節

    c. datetime:非空8字節,可空9字節

舉例:

(7). ref

 顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值。

(8). rows

 根據表統計信息及索引選用情況,大致估算出找到所需的記錄需要讀取的行數。

注:並不是結果集中的實際行數。

(9). Extra

 包含不適合在其他列中顯示但十分重要的額外信息。

 A. using index :使用覆蓋索引的時候就會出現 (我們所喜歡的)

explain select film_id from film_actor where film_id = 1;

 B. using where:使用 where 語句來處理結果,查詢的列未被索引覆蓋。

explain select * from actor where name = 'a';

 

 C. using index condition:查找使用了索引,查詢的列不完全被索引覆蓋,where條件中是一個前導列的范圍; (mysql5.6以后的新特性)

explain select * from film_actor where film_id > 1;

 D. using index & using where:查找使用了索引,但是需要的數據都在索引列中能找到,所以不需要回表查詢數據   (我們所喜歡的)

 E. Using temporary:mysql需要創建一張臨時表來處理查詢。出現這種情況一般是要進行 優化的,首先是想到用索引來優化。

情況1:actor.name沒有索引,此時創建了張臨時表來distinct

explain select distinct name from actor;

 

情況2: film.name建立了idx_name索引,此時查詢時extra是using index,沒有用臨時表

 explain select distinct name from film;

 F. Using filesort:將用外部排序而不是索引排序,數據較小時從內存排序,否則需要在磁盤 完成排序。這種情況下一般也是要考慮使用索引來優化的。

情況1:actor.name未創建索引,會瀏覽actor整個表,保存排序關鍵字name和對應的id,然后排序name並檢索行記錄。

explain select * from actor order by name;

情況2: film.name建立了idx_name索引,此時查詢時extra是using index

 explain select * from film order by name;

 G. Select tables optimized away:使用某些聚合函數(比如 max、min)來訪問存在索引 的某個字段是

 explain select min(id) from film;

 H. Using join buffer :使用了連接緩存。

 I. impossible where : WHERE 子句的值總是false,不能用來獲取任何元組。

 

三. 最佳實踐入門

數據准備:

  name-age-position 三個字段組成聯合索引,id為主鍵索引,hire_time字段上沒有索引。

CREATE TABLE `employees` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
 `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',
 `position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',
 `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';

 INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
 INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei',23,'dev',NOW());
 INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());

 

1. 全值匹配

 

2. 最左前綴法則

  如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。 (注:在覆蓋索引面前,最左前綴原則無效)

(1). 全表掃描,索引失效

EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';

(2). 用到了name索引

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';

3. 不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉 向全表掃描

EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';

4. .存儲引擎不能使用索引中范圍條件右邊的列

  用到了name 和 age索引,position失效,  key_len= 3*24+2+4=78

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';

5. 盡量使用覆蓋索引(只訪問索引的查詢(索引列包含查詢列)),減少select *語句

(1). 索引覆蓋

EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';

(2). 索引未覆蓋 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';

6. mysql在使用不等於(!=或者<>)的時候無法使用索引會導致全表掃描

EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';

7. is null,is not null 也無法使用索引

EXPLAIN SELECT * FROM employees WHERE name is null

8. like以通配符開頭('%abc...')mysql索引失效會變成全表掃描操作

(1). %在左邊,索引失效

EXPLAIN SELECT * FROM employees WHERE name like '%Lei';

(2). %在右邊,索引有效

EXPLAIN SELECT * FROM employees WHERE name like 'Lei%';

PS: 解決like'%字符串%'索引不被使用的方法?

使用覆蓋索引!!!!!

EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';

9. 字符串不加單引號索引失效

(1). 有效

EXPLAIN SELECT * FROM employees WHERE name = '1000';

(2). 失效

EXPLAIN SELECT * FROM employees WHERE name = 1000;

10. 少用or或in,用它查詢時,mysql不一定使用索引,mysql內部優化器會根據檢索比例、 表大小等多個因素整體評估是否使用索引.

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

11. 范圍查詢優化

准備:事先插入1萬條數據,給age字段添加單獨的索引

ALTER TABLE `employees`
ADD INDEX `idx_age` (`age`) USING BTREE ;

(1). 大范圍查找

剖析:

  沒走索引原因:mysql內部優化器會根據檢索比例、表大小等多個因素整體評估是否使用索 引。比如這個例子,可能是由於單次數據量查詢過大導致優化器最終選擇不走索引。

(2). 縮小范圍優化查找,使用到了索引

 

最后總結:

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM