MySQL索引原理


內容概述

1.什么是索引?
2.使用索引的好處
3.索引分類
4.索引的數據結構
5.索引管理
6.正確的使用索引

內容詳細

1.什么是索引?

索引是存儲引擎中的一種數據結構,或者說數據的組織方式,又稱為鍵key,是存儲引擎用於快速找到記錄的一種數據結構。

為數據建立索引好比為字典創建音序表,如果要查字,不用音序表的話查詢非常困難,建立索引就是這個道理

索引是數據的組織方式,將數據按索引規定的結構組織成一種樹型結構,該樹叫B+樹

2.使用索引的好處

在生產環境中我們遇到最多且容易出問題的是一些比較復雜的查詢操作,所以我們要對查詢語句進行優化,要加速查詢的話,就需要用到索引。

關於索引:
索引並不是越多越好,並且最好提前創建索引

# 索引多了可能會影響到數據庫寫操作,硬盤IO變高
如果某一張表的ibd文件中創建了很多索引樹,意味着進行寫操作時(update語句),多個索引樹都要發生變化,從而導致硬盤IO變高。

索引的本質就是不斷地縮小數據的范圍選出最終想要的結果,同時將隨機事件變成順序事件。

知識儲備:
# 磁盤的預讀
計算機訪問一個地址的數據時,與其相鄰的數據也會很快被訪問到。
每一次IO讀取的數據我們稱為一頁(page),具體一頁有多大數據跟操作系統有關,一般為4K到8K,也就是讀取一頁內的數據時,實際才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助。

查詢優化:
# SQL層面
添加適當的索引
優化SQL語句的邏輯,盡量使查詢所涉及的行變少

# 架構層面
MySQL集群
主從復制
讀寫分離
MySQL集群前加入Redis緩存

# 補充:
等值查詢:	where語句 =
范圍查詢:	where語句 < > ...

3.索引分類

#===== B+樹索引(InnoDB存儲引擎默認)
聚集索引:即主鍵索引,primary key
用途:
1.加速查找
2.約束(不為空、不能重復)

唯一索引:unique
用途:
1.加速查找
2.約束(不能重復)

普通索引:index
用途:
1.加速查找

聯合索引:
primary key(id,name):聯合主鍵索引
unique(id,name):聯合唯一索引
index(id,name):聯合普通索引

#===== HASH索引(哈希索引,查詢單條塊,范圍查詢較慢)
原理:
將數據打散再去查詢
InnoDB和Myisam都不支持,設置完還是B樹
Memory存儲引擎支持

# 值得注意的是,InnoDB存儲引擎的內存中的架構中包含一個自適應哈希索引,這個自適應哈希索引是InnoDB為了加速查詢性能而自動創建的

#===== FULLTEXT:全文索引 (只可以用在MyISAM引擎中)
通過關鍵字的匹配來進行查詢,,類似於like的模糊匹配
like + %在文本比較少時是合適的,但對於大量的文本數據檢索會非常慢
全文索引在大量的數據面前能比like塊得多,但是准確度很低

#===== RTREE:R樹索引
RTREE在mysql很少使用,僅支持geometry數據類型
geometry數據類型一般填寫經緯度那樣的數據
支持該類型的存儲引擎只有MyISAM、BDb、InnoDb、NDb、Archive幾種。
RTREE范圍查找很強,但Btree也不弱

# 不同的存儲引擎支持的索引類型也不一樣
InnoDB支持事務,支持行級別鎖定,支持B-tree、Full-text 等索引,不支持 Hash 索引;

MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;

Memory不支持事務,支持表級鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;

NDB支持事務,支持行級別鎖定,支持Hash索引,不支持B-tree、Full-text 等索引;

Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;

4.索引的數據結構

InnoDB存儲引擎默認的索引結構為B+樹,而B+樹是由二叉樹、平衡二叉樹、B樹再到B+樹一路演變過來的

二叉查找樹

img

提取每一條記錄的id值作為key值,value為本行完整記錄

id user
10 zs
7 ls
13 ww
5 zl
8 xw
12 xm
17 dy
以key值的大小為基礎構建二叉樹,如上圖所示
二叉查找樹的特點就是任何節點的左子節點的鍵值都小於當前節點的鍵值,右子節點的鍵值都大於當前節點的鍵值。
頂端的節點我們稱為根節點,沒有子節點的節點我們稱之為葉節點。

如果我們需要查找id=12的用戶信息
select * from user where id=12;

查找流程如下:
1.將根節點作為當前節點,把12與當前節點的鍵值10比較,12大於10,接下來我們把當前節點>的右子節點作為當前節點
2.繼續把12和當前節點的鍵值13比較,發現12小於13,把當前節點的左字節點作為當前節點
3.把12和當前節點的鍵值12對比,12等於12,滿足條件,我們從當前節點中取出data,即id=1>2,name=xm

# 利用二叉樹我們需要3次即可找到匹配的數據,如果在表中一條條的查找的話,我們需要6次才能找到。

平衡二叉樹

我們回到二叉查找樹的特點上,只論二叉查找樹,它的特點只是:任何節點的左子節點的鍵值都小於當前節點的鍵值,右子節點的鍵值都大於當前節點的鍵值。
# 但某種情況下二叉查找樹變成了鏈表,這種現象會導致二叉查找樹變得不平衡,也就是高度太高,從而導致查找效率的不穩定。

為了解決這個問題,我們需要保證二叉查找樹一直保持平衡,就需要用到平衡二叉樹了。平衡二叉樹又稱AVL樹,在滿足二叉查找樹特性的基礎上,要求每個節點的左右子樹的高度不能超過1。
下圖進行對比:

img

由平衡二叉樹的構造我們可以發現第一張圖中的二叉樹其實就是一棵平衡二叉樹。平衡二叉樹保證了樹的構造是平衡的,當我們插入或刪除數據導致不滿足平衡二叉樹不平衡時,平衡二叉樹會進行調整樹上的節點來保持平衡。

具體的調整方式這里就不介紹了。平衡二叉樹相比於二叉查找樹來說,查找效率更穩定,總體的查找速度也更快。

B樹

平衡二叉樹會因為數據量變大而導致樹的高度變高,在查找數據時會進行很多次的磁盤IO,查找數據的效率也會變得極低。

綜上,我們要在平衡二叉樹的基礎上,把更多的節點放入一個磁盤塊中,那么平衡二叉樹的弊端也就解決了,即構建一個單節點可以存儲多個鍵值對的平衡樹,這就是B樹

img

從上圖可以看出,B樹相對於平衡二叉樹,每個節點存儲了更多的鍵值(key)和數據(data),並且每個節點擁有更多的節點,子節點的個數一般為階。

上圖中的B樹為3階B樹,高度也會很低,基於這個特性,B樹查找數據讀取磁盤的次數就會很少,數據庫的查找效率也會比平衡二叉樹高很多。

# B樹的不足:對於范圍查詢,或者說排序操作,B樹也不能做得很好。

B+樹

B+樹是對B樹的進一步優化。讓我們先來看下B+樹的結構圖:

img

1.B+樹非葉子節點non-leaf node上是不存儲數據的,僅存儲鍵,而B樹的非葉子加點中不僅存儲鍵,也會存儲數據。B+樹之所以這么做的意義在於:樹一個節點就是一個頁,而數據庫中頁的大小是固定的,innodb存儲引擎默認一頁為16KB,所以在頁大小固定的前提下,能往一個頁中放入更多的節點,相應的樹的階數(節點的子節點樹)就會更大,那么樹的高度必然更矮更胖,如此一來我們查找數據進行磁盤的IO次數有會再次減少,數據查詢的效率也會更快。

2.B+樹的階數是等於鍵的數量的,例如上圖,我們的B+樹中每個節點可以存儲3個鍵,3層B+樹存可以存儲3*3*3=27個數據。所以如果我們的B+樹一個節點可以存儲1000個鍵值,那么3層B+樹可以存儲1000×1000×1000=10億個數據。而一般根節點是常駐內存的,所以一般我們查找10億數據,只需要2次磁盤IO,真是屌炸天的設計。

3、因為B+樹索引的所有數據均存儲在葉子節點leaf node,而且數據是按照順序排列的。那么B+樹使得范圍查找,排序查找,分組查找以及去重查找變得異常簡單。而B樹因為數據分散在各個節點,要實現這一點是很不容易的。
而且B+樹中各個頁之間也是通過雙向鏈表連接的,葉子節點中的數據是通過單向鏈表連接的。其實上面的B樹我們也可以對各個節點加上鏈表。其實這些不是它們之前的區別,是因為在mysql的innodb存儲引擎中,索引就是這樣存儲的。也就是說上圖中的B+樹索引就是innodb中B+樹索引真正的實現方式,准確的說應該是聚集索引(聚集索引和非聚集索引下面會講到)。

通過上圖可以看到,在innodb中,我們通過數據頁之間通過雙向鏈表連接以及葉子節點中數據之間通過單向鏈表連接的方式可以找到表中所有的數據。

MyISAM中的B+樹索引實現與innodb中的略有不同。在MyISAM中,B+樹索引的葉子節點並不存儲數據,而是存儲數據的文件地址。

5.索引管理

創建/刪除索引的語法

# 方法一:創建表時
create table 表名 (
    字段名1 數據類型 [完整性約束條件...],
    字段名2 數據類型 [完整性約束條件...],
    [unique | fulltext | spatial] index | key
    [索引名] (字段名[(長度)] [asc | desc])
    );
    
案例:
mysql> create table t11 (id int primary key);
Query OK, 0 rows affected (0.01 sec)

mysql> show create table t11\G
*************************** 1. row ***************************
       Table: t11
Create Table: CREATE TABLE `t11` (
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

# 方法二:create在已存在的表上創建索引
create [unique | fulltext | spatial ] index 索引名 on 表名 (字段名[(長度)] [asc | desc]);

案例:
mysql> create unique index index12 on t12 (id);
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table t12\G
*************************** 1. row ***************************
       Table: t12
Create Table: CREATE TABLE `t12` (
  `id` int(11) DEFAULT NULL,
  UNIQUE KEY `index12` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

# 方法三:alter table 在已存在的表上創建索引
alter table 表名 add [unique | fulltext | spatial] index 索引名 (字段名[(長度)] [asc | desc]);

案例:
mysql> create table t13(id int);
Query OK, 0 rows affected (0.00 sec)

mysql> alter table t13 add unique index index13 (id);
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table t13\G
*************************** 1. row ***************************
       Table: t13
Create Table: CREATE TABLE `t13` (
  `id` int(11) DEFAULT NULL,
  UNIQUE KEY `index13` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

# 刪除索引
drop index 索引名 on 表名字;
alter table 表名字 drop index 索引名字;

# 刪除主鍵索引
alter table 表名字 drop primary key;

案例:
mysql> drop index index13 on t13;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table t13\G
*************************** 1. row ***************************
       Table: t13
Create Table: CREATE TABLE `t13` (
  `id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

# 查看索引
方法一:
mysql> desc t12;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | YES  | UNI | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)

方法二:
mysql> show index from t12\G
*************************** 1. row ***************************
        Table: t12
   Non_unique: 0
     Key_name: index12
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)

索引示例

# 主鍵索引(聚集索引)
# 創建主鍵索引

mysql> alter table student add primary key pri_id(id);
mysql> create table student(id int not null, primary key(id)); 
mysql> create table student(id int not null primary key auto_increment comment '學號');
# 注意:
database 可以寫為 schema
index 可以寫為 key

# 唯一鍵索引
# 創建唯一鍵索引

mysql> alter table country add unique key uni_name(name);
mysql> create table student(id int unique key comment '學號');
mysql> create unique key index index_name on table_name(id);

# 普通索引(輔助索引)
# 普通索引的創建

mysql> alter table student add index idx_gender(gender);
mysql> create index index_name on table_name (column_list);

# 創建前綴索引
按照該列數據的前n個字母創建索引
mysql> alter table student add index idx_name(name(4));

# 全文索引
# 針對content做了全文索引:
CREATE TABLE t1 (
id int NOT NULL AUTO_INCREMENT,
title char(255) NOT NULL,
content text,
PRIMARY KEY (id),
FULLTEXT (content));

查找時:
select * from table where match(content) against('想2查詢的字符串');

6.正確的使用索引

並不是創建了索引就一定會加快查詢速度,若想利用索引達到預想的提高查詢速度的效果,我們添加索引時,必須遵循以下問題。

范圍問題

條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like

# 如果沒有明確指定到底找哪個字段的值,而是指定了一個范圍,如果這個范圍比較大,那么則跟全表掃描沒有多大區別

where語句后跟< > != 或 between...and...或 like
# like關鍵字當使用%前置的時候,是無法命中索引的,進而查詢速度很慢,所以企業中應盡量避免使用%前置

查詢優化神器-explain

explain命令具體用法和字段含義可以參考官網explain-output,這里需要強調rows是核心指標,絕大部分rows小的語句執行一定很快(有例外)。所以優化語句基本都是在優化rows。

字段詳解
1.id:
包含一組數字,表示查詢中執行select子句或操作表的順序

Example(id相同,執行順序由上至下)
如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行

id如果相同,可以認為是一組,從上往下順序執行;在所有組中,id越大,優先級越高,越先執行。

2.select_type
表示查詢中每個select子句的類型(簡單or復雜)
a. SIMPLE:查詢中不包含子查詢或者UNION
b. 查詢中若包含任何復雜的子部分,最外層查詢則被標記為:PRIMARY
c. 在SELECT或WHERE列表中包含了子查詢,該子查詢被標記為:SUBQUERY
d. 在FROM列表中包含的子查詢被標記為:DERIVED(衍生)用來表示包含在from子句中的子查詢的select,mysql會遞歸執行並將結果放到一個臨時表中。服務器內部稱為"派生表",因為該臨時表是從子查詢中派生出來的
e. 若第二個SELECT出現在UNION之后,則被標記為UNION;若UNION包含在FROM子句的子查詢中,外層SELECT將被標記為:DERIVED
f. 從UNION表獲取結果的SELECT被標記為:UNION RESULT

SUBQUERY和UNION還可以被標記為DEPENDENT和UNCACHEABLE。
DEPENDENT意味着select依賴於外層查詢中發現的數據。
UNCACHEABLE意味着select中的某些 特性阻止結果被緩存於一個item_cache中。

第一行:id列為1,表示第一個select,select_type列的primary表 示該查詢為外層查詢,table列被標記為<derived3>,表示查詢結果來自一個衍生表,其中3代表該查詢衍生自第三個select查詢,即id為3的select。
第二行:id為3,表示該查詢的執行次序為2( 4 => 3),是整個查詢中第三個select的一部分。因查詢包含在from中,所以為derived。
第三行:select列表中的子查詢,select_type為subquery,為整個查詢中的第二個select。
第四行:select_type為union,說明第四個select是union里的第二個select,最先執行。
第五行:代表從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行union操作。

3、 type   
表示MySQL在表中找到所需行的方式,又稱“訪問類型”。

常用的類型有: ALL, index,  range, ref, eq_ref, const, system, NULL(從左到右,性能從差到好)

ALL:Full Table Scan,    

index: Full Index Scan,index與ALL區別為index類型只遍歷索引樹

range:只檢索給定范圍的行,使用一個索引來選擇行

ref: 表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值

eq_ref: 類似ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連接中使用primary key或者 unique key作為關聯條件

const、system: 當MySQL對查詢某部分進行優化,並轉換為一個常量時,使用這些類型訪問。如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量,system是const類型的特例,當查詢的表只有一行的情況下,使用system

NULL: MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列里選取最小值可以通過單獨索引查找完成。

4、table
顯示這一行的數據是關於哪張表的,有時不是真實的表名字,看到的是derivedx(x是個數字,我的理解是第幾步執行的結果)

5、possible_keys
指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用

該列完全獨立於EXPLAIN輸出所示的表的次序。這意味着在possible_keys中的某些鍵實際上不能按生成的表次序使用。
如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢性能。如果是這樣,創造一個適當的索引並且再次用EXPLAIN檢查查詢

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

7、key_len
表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度(key_len顯示的值為索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的)

不損失精確性的情況下,長度越短越好  

8、ref
表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值 

9、rows
 表示MySQL根據表統計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數
 
10、Extra
該列包含MySQL解決查詢的詳細信息,有以下幾種情況:

Using where:列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候,表示mysql服務器將在存儲引擎檢索行后再進行過濾

Using temporary:表示MySQL需要使用臨時表來存儲結果集,常見於排序和分組查詢

Using filesort:MySQL中無法利用索引完成的排序操作稱為“文件排序”

Using join buffer:改值強調了在獲取連接條件時沒有使用索引,並且需要連接緩沖區來存儲中間結果。如果出現了這個值,那應該注意,根據查詢的具體情況可能需要添加索引來改進能。

Impossible where:這個值強調了where語句會導致沒有符合條件的行。

Select tables optimized away:這個值意味着僅通過使用索引,優化器可能僅從聚合函數結果中返回一行。


免責聲明!

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



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