1. 程式人生 > 實用技巧 >10. 索引的設計和使用

10. 索引的設計和使用

10.1 索引概述

資料庫索引:是資料庫管理系統中一個排序的資料結構,以協助快速查詢、更新資料表中資料。索引的實現通常使用B樹及其變種B+樹。

MySQL索引:索引用於快速查詢具有特定列值的行。沒有索引,MySQL必須從第一行開始,然後通讀整個表以找到相關的行。

索引的優點

  • 通過建立唯一性索引,可以保證資料庫表中每一行資料的唯一性。
  • 加快資料的檢索速度。
  • 減少磁碟I/O次數
  • 加速表和表之間的連線。
  • 在使用分組和排序子句進行資料檢索時,同樣可以顯著減少查詢中分組和排序的時間。
  • 通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的效能。

索引並不是萬金油,增加索引也有許多不利的方面。

  • 建立索引和維護索引要耗費時間,這種時間隨著資料量的增加而增加。
  • 索引需要佔物理空間,除了資料表佔資料空間之外,每一個索引還要佔一定的物理空間,如果要建立聚簇索引,那麼需要的空間就會更大。
  • 對錶中的資料進行增刪改的時候,索引也需要動態的維護,降低寫操作效能,減緩表的修改速度

10.2 資料庫索引的分類

可以在資料庫設計器中建立五種型別的索引:主鍵索引、唯一索引、普通索引、複合索引、全文索引。其中主鍵索引也可以叫做聚集索引,非主鍵索引稱為非聚集索引。

主鍵索引

資料庫表經常有一列或列組合,其值唯一標識表中的每一行。該列稱為表的主鍵。

在資料庫關係圖中為表定義主鍵將自動建立主鍵索引,主鍵索引是唯一索引的特定型別。該索引要求主鍵中的每個值都唯一。當在查詢中使用主鍵索引時,它還允許對資料的快速訪問。

唯一索引

唯一索引是不允許其中任何兩行具有相同索引值的索引,唯一性,可以為空

當現有資料中存在重複的鍵值時,大多數資料庫不允許將新建立的唯一索引與表一起儲存。資料庫還可能防止新增將在表中建立重複鍵值的新資料。例如,如果在employee表中職員的姓(name)上建立了唯一索引,則任何兩個員工都不能同姓。

普通索引

用表中的普通列構建的索引,沒有任何限制

複合索引

用多個列組合構建的索引,這多個列中的值不允許有空值,遵循最左匹配原則。如組合索引index(col1,col2,col3),使用col2和col3的查詢是不會使用索引的。

全文索引

主要用於大文字資訊建立索引

10.3 聚集索引和非聚集索引

聚集索引的理解:

我對索引的的理解是這樣的:

如果一個表沒有加索引,資料按順序一條一條的在磁碟上按插入順序儲存著。

如果一張表一旦加了索引,比如加了自增主鍵,那麼表中的資料在磁碟中就不是按順序排列的,而是變成了樹狀結構,也就是我們常說的平衡樹,換句話說就是整個表都變成了一個索引樹,也就是所謂的聚簇索引。這也是為什麼一個表中只有一個主鍵(可以是多個欄位,聯合主鍵),因為自增主鍵的根就是根據一定演算法把表的資料按照一定格式轉換成平衡樹存放在磁碟上的。

聚集索引:表資料存放的物理順序與索引順序一致

非聚集索引:表資料存放的物理順序與索引順序不一致

相當於順序表和連結串列之間的差異。

根本區別是表記錄的排列順序和與索引的排列順序是否一致。

10.3.1 聚集索引

  • 聚集索引的葉子結點就是資料結點。
  • 聚簇索引的資料表和主鍵索引儲存在一起

聚集索引中表記錄的排列順序和索引的排列順序保持一致,所以查詢效率相當快。只要找到第一個索引記錄的值,其餘的連續性的記錄也一定是連續存放的。

假設執行一個SQLselect * from where id=50,查詢流程就是平衡樹關鍵字的查詢

很多儲存引擎在B+Tree的基礎上進行了優化,添加了指向相鄰葉節點的指標,形成了帶有順序訪問指標的B+Tree,這樣做是為了提高區間查詢的效率,只要找到第一個值那麼就可以順序的查詢後面的值。

下面更好的解釋了這個概念

10.3.2 非聚集索引

  • 非聚簇索引的葉節點仍然是索引節點,只不過有一個指標指向對應的資料塊。

  • 非聚簇索引的資料表和索引表是分開儲存的。

非聚集索引這棵樹中所有的節點都來自於表中二級索引(非聚集索引)欄位。假如給user表的name欄位加上索引 , 那麼索引就是由name欄位中的值構成,在資料改變時, DBMS需要一直維護索引結構的正確性。如果給表中多個欄位加上索引 ,那麼就會出現多個獨立的索引結構每個索引(非聚集索引)互相之間不存在關聯

每次給欄位建一個新索引,欄位中的資料就會被複制一份出來,用於生成索引。因此,給表新增索引,會增加表的體積,佔用磁碟儲存空間。

非聚集索引和聚集索引的區別在於,通過聚集索引可以查到需要查詢的資料,而通過非聚集索引可以查到記錄對應的主鍵值 ,再使用主鍵的值通過聚集索引查詢到需要的資料回表查詢),如下圖

聚集和非聚集索引不管以任何方式查詢表,最終都會利用主鍵通過聚集索引來定位到資料,聚集索引(主鍵)是通往真實資料所在的唯一路徑。

不過,有一種例外可以不使用聚集索引就能查詢出所需要的資料這種非主流的方法稱之為覆蓋索引查詢,也就是平時所說的複合索引或者多欄位索引查詢

索引參考:

10.4 索引的設計原則

(1) 在經常搜尋的列上建立索引,可以加快搜索的速度。 搜尋的索引列,不一定是所要選擇的列。換句話說,最適合索引的列是出現在 WHERE 子句中的列,或連線子句中指定的列,而不是出現在 SELECT 關鍵字後的選擇列表中的列。

(2) 使用唯一索引

(3) 使用段索引。如果對字串列進行索引,應該指定一個字首長度,只要有可能就應該這樣做。例如,如果有一個 CHAR(200)列,如果在前 10 個或 20 個字元內,多數值是惟一的,那麼就不要對整個列進行索引。對前10個或20個字元進行索引能夠節省大量索引空間,也可能會使查詢更快。較小的索引涉及的磁碟 IO 較少,較短的值比較起來更快。

(3) 利用最左字首匹配原則

(4) 不要過度索引。不要以為索引“越多越好”,什麼東西都用索引是錯誤的。每個額外的索引都要佔用額外的磁碟空間,並降低寫操作的效能。在修改表的內容時,索引必須進行更新,有時可能需要重構,因此,索引越多,所花的時間越長。

10.5 索引的使用

1、建表時建立索引

create table tb_name (
	col_name 資料型別 [完整性約束條件],
	...,
	[unique | fulltext | spatial] index|key [索引名](欄位名1[(長度)] [ASC]|[DESC]) [using 索		引方法]
);

引數:

  • unique:唯一性索引
  • fulltext:全文索引
  • spatial:空間索引
  • index和key:用於指定欄位為索引,兩者作用是一樣的,二選一。
  • 索引名:可選,建立索引時指定名稱。
  • 欄位名1:指定索引對應的欄位的名稱,該欄位必須是前面定義好的欄位。
  • 長度:可選。指索引的長度,必須是字串型別才可以使用。
  • ASC:可選。表示升序排列。
  • DESC:可選。表示降序排列。

索引示例:

mysql> create table t (
    -> id int comment 'ID標識',
    -> name varchar(20) comment '姓名',
    -> age int comment '年齡',
    -> addr varchar(100),
    -> primary key PK_ID (id),
    -> index index_name (name)
    -> );

2、建表後建立約束

alter table tb_name add  [UNIQUE | FULLTEXT | SPATIAL]  INDEX | KEY  [索引名] (欄位名1 [(長度)] [ASC | DESC]) [USING 索引方法];
或者
CREATE  [UNIQUE | FULLTEXT | SPATIAL]  INDEX  索引名 ON  表名(欄位名) [USING 索引方法];

示例:

mysql>  alter table t add unique index (addr);

3、檢視已建立的索引

show index from tb_name;

【注】索引預設是BTREE索引

4、索引的刪除

drop index 索引名 on tb_name;
alter table tb_name drop index 索引名;

5、檢視SQL語句對索引的使用情況

select語句之前新增explain即可

請注意第二個和第三個查詢,一個用到了索引,一個沒有用到索引。

BTREE索引觸發原則:

當使用 >、<、>= 、BETWEEN、!=、<>或者LIKE ’pattern'(其中patter不以萬用字元開始)操作符時,都可以使用相關列上的索引。

explain查詢引數:

id:SELECT識別符。這是SELECT的查詢序列號。

select_type:SELECT型別。

  1. SIMPLE: 簡單SELECT(不使用UNION或子查詢)
  2. PRIMARY: 最外面的SELECT
  3. UNION:UNION中的第二個或後面的SELECT語句
  4. DEPENDENT UNION:UNION中的第二個或後面的SELECT語句,取決於外面的查詢
  5. UNION RESULT:UNION的結果
  6. SUBQUERY:子查詢中的第一個SELECT
  7. DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢
  8. DERIVED:匯出表的SELECT(FROM子句的子查詢)

table:表名

type:聯接型別。是SQL效能的非常重要的一個指標,結果值從好到壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。

一般來說,得保證查詢至少達到range級別。

  1. system:表僅有一行(=系統表)。這是const聯接型別的一個特例。
  2. const:表最多有一個匹配行,它將在查詢開始時被讀取。因為僅有一行,在這行的列值可被優化器剩餘部分認為是常數。const用於用常數值比較PRIMARY KEY或UNIQUE索引的所有部分時。
  3. eq_ref:對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是最好的聯接型別,除了const型別。它用在一個索引的所有部分被聯接使用並且索引是UNIQUE或PRIMARY KEY。eq_ref可以用於使用= 操作符比較的帶索引的列。比較值可以為常量或一個使用在該表前面所讀取的表的列的表示式。
  4. ref:對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。如果聯接只使用鍵的最左邊的字首,或如果鍵不是UNIQUE或PRIMARY KEY(換句話說,如果聯接不能基於關鍵字選擇單個行的話),則使用ref。如果使用的鍵僅僅匹配少量行,該聯接型別是不錯的。ref可以用於使用=或<=>操作符的帶索引的列。
  5. ref_or_null:該聯接型別如同ref,但是添加了MySQL可以專門搜尋包含NULL值的行。在解決子查詢中經常使用該聯接型別的優化。
  6. index_merge:該聯接型別表示使用了索引合併優化方法。在這種情況下,key列包含了使用的索引的清單,key_len包含了使用的索引的最長的關鍵元素。
  7. unique_subquery:該型別替換了下面形式的IN子查詢的ref:value IN (SELECT primary_key FROMsingle_table WHERE some_expr);unique_subquery是一個索引查詢函式,可以完全替換子查詢,效率更高。
  8. index_subquery:該聯接型別類似於unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引:value IN (SELECT key_column FROM single_table WHERE some_expr)
  9. range:只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。在該型別中ref列為NULL。當使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比較關鍵字列時,可以使用range
  10. index:該聯接型別與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引檔案通常比資料檔案小。
  11. all:對於每個來自於先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,並且通常在它情況下很差。通常可以增加更多的索引而不要使用ALL,使得行能基於前面的表中的常數值或列值被檢索出。

possible_keys:possible_keys列指出MySQL能使用哪個索引在該表中找到行。注意,該列完全獨立於EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用。

key:key列顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

key_len:key_len列顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。注意通過key_len值我們可以確定MySQL將實際使用一個多部關鍵字的幾個部分。

ref:ref列顯示使用哪個列或常數與key一起從表中選擇行。

rows:rows列顯示MySQL認為它執行查詢時必須檢查的行數。

Extra:該列包含MySQL解決查詢的詳細資訊。

explain命令解釋

10.6 索引命中

1、萬用字元在前,模糊查詢不使用索引

2、union、in、or 都能夠命中索引,建議使用 in

3、聯合索引最左字首原則(又叫最左側查詢)

如果在(a,b,c)三個欄位上建立聯合索引,那麼它能夠加快 a | (a,b) | (a,b,c) 三組查詢速度。

4、範圍列可以用到索引

  • 範圍條件有:<、<=、>、>=、between等。
  • 範圍列可以用到索引(聯合索引必須是最左字首),但是範圍列後面的列無法用到索引索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引

如有索引 (a,b,c,d),查詢條件 a=1 and b=2 and c>3 and d=4,則會在每個節點依次命中a、b、c,無法命中d。(c已經是範圍查詢了,d肯定是排不了序了)

範圍查詢不一定用到索引

如果索引列資料量大的情況下,那麼如果使用索引列查詢不及普通查詢的話,也有可能用不到索引。

10.7 最左匹配原則的解釋

建立表

create` `table` `test(
a ``int` `,
b ``int``,
c ``int``,
d ``int``,
key` `index_abc(a,b,c)
)engine=InnoDB ``default` `charset=utf8;

插入10000條資料,當我在執行的時候,以為會很快執行,但沒想到這10000條資料往磁碟寫花費了接近2min,

不明白為什麼?

DROP` `PROCEDURE` `IF EXISTS proc_initData;
DELIMITER $
CREATE` `PROCEDURE` `proc_initData()
BEGIN
DECLARE` `i ``INT` `DEFAULT` `1;
WHILE i<=10000 DO
  ``INSERT` `INTO` `test(a,b,c,d) ``VALUES``(i,i,i,i);
  ``SET` `i = i+1;
END` `WHILE;
END` `$
CALL proc_initData();
explain select * from test where a<10 ;

explain select * from test where a<10 and b <10;

explain select * from test where a<10 and b <10 and c<10;

上述查詢都顯示用到了組合索引,但是考慮下面這種情況:

explain select * from test where b<10 and a <10;

explain select * from test where b<10 and a <10 and c<10;

explain顯示查詢也用到了索引,不是最左匹配嗎?

mysql會自動優化這些條件的順序,以匹配儘可能多的索引列。

其實,mysql查詢優化器幫我們解析糾正這條語句以什麼樣的順序執行效率最高,有點自動校正的意思。

但是如果執行

explain select * from test where b<10 and c <10;

explain select * from test where a<10 and c <10;

會發現b<10 and c <10,沒有用到索引?而 a<10 and c <10用到了

因為B+樹的資料項是複合的資料結構,比如建立組合索引(name,age,sex),B+樹是從左到右來建立搜尋樹的,比如查詢(張三,20,F),b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的資料;但當(20,F)這樣的沒有name的資料的時候,b+樹就不知道下一步該查哪個節點,因為建立搜尋樹的時候name就是第一個比較因子,必須要先根據name來搜尋才能知道下一步去哪裡查詢。比如當(張三,F)這樣的資料來檢索時,b+樹可以用name來指定搜尋方向,但下一個欄位age的缺失,所以只能把名字等於張三的資料都找到,然後再匹配性別是F的資料了, 這個是非常重要的性質,即索引的最左匹配特性。