MySQL索引

一. 存儲引擎

1. 什麼是存儲引擎?

與其他數據庫例如Oracle 和SQL Server等數據庫中只有一種存儲引擎不同的是,MySQL有一個被稱爲“Pluggable Storage Engine Architecture”(可替換存儲引擎架構)的特性,也就意味着MySQL數據庫提供了多種存儲引擎。用戶可以根據不同的需求爲數據表選擇不同的存儲引擎,用戶也可以根據自己的需要編寫自己的存儲引擎。MySQL數據庫在實際的工作中其實分爲了語句分析層和存儲引擎層,其中語句分析層就主要負責與客戶端完成連接並且事先分析出SQL語句的內容和功能,而存儲引擎層則主要負責接收來自語句分析層的分析結果,完成相應的數據輸入輸出和文件操作。簡而言之,就是如何存儲數據、如何爲存儲的數據建立索引和如何更新、查詢數據等技術的實現方法。因爲在關係數據庫中數據的存儲是以表的形式存儲的,所以存儲引擎也可以稱爲表類型(即存儲和操作此表的類型)。

2. 存儲引擎種類

存儲引擎 說明
MyISAM 高速引擎,擁有較高的插入,查詢速度,但不支持事務
InnoDB 5.5版本後MySQL的默認數據庫,支持事務和行級鎖定,比MyISAM處理速度稍慢
ISAM MyISAM的前身,MySQL5.0以後不再默認安裝
MRG_MyISAM(MERGE) 將多個表聯合成一個表使用,在超大規模數據存儲時很有用
Memory 內存存儲引擎,擁有極高的插入,更新和查詢效率。但是會佔用和數據量成正比的內存空間。只在內存上保存數據,意味着數據可能會丟失
Falcon 一種新的存儲引擎,支持事物處理,傳言可能是InnoDB的替代者
Archive 將數據壓縮後進行存儲,非常適合存儲大量的獨立的,作爲歷史記錄的數據,但是隻能進行插入和查詢操作
CSV CSV 存儲引擎是基於 CSV 格式文件存儲數據(應用於跨平臺的數據交換)

接下來我們就介紹兩種在實際開發中使用最多的兩種引擎【MyISAM】和【InnoDB】。

3. MyISAM 引擎

這種引擎是MySQL最早提供的。這種引擎又可以分爲靜態MyISAM、動態MyISAM 和壓縮MyISAM三種:

  • 靜態MyISAM:如果數據表中的各數據列的長度都是預先固定好的,服務器將自動選擇這種表類型。因爲 數據表中每一條記錄所佔用的空間都是一樣的,所以這種表存取和更新的效率非常高。當數據受損時,恢復工作也比較容易做。

  • 動態MyISAM:如果數據表中出現varchar、xxxtext或xxxBLOB字段時,服務器將自動選擇這種表類型。相對於靜態MyISAM,這種表存儲空間比較小,但由於每條記錄的長度不一,所以多次修改數據後,數據表中的數據就可能離散的存儲在內存中,進而導致執行效率下降。同時,內存中也可能會出現很多碎片。因此,這種類型的表要經常用optimize table 命令或優化工具來進行碎片整理。

  • 壓縮MyISAM:以上說到的兩種類型的表都可以用myisamchk工具壓縮。這種類型的表進一步減小了佔用的存儲,但是這種表壓縮之後不能再被修改。另外,因爲是壓縮數據,所以這種表在讀取的時候要先時行解壓縮。

當然不管是何種MyISAM表,目前它都不支持事務,行級鎖和外鍵約束的功能,這就意味着有事務處理需求的表,不能使用MyISAM存儲引擎。MyISAM存儲引擎特別適合在以下幾種情況下使用:

  • 選擇密集型的表。MyISAM存儲引擎在篩選大量數據時非常迅速,這是它最突出的優點。

  • 插入密集型的表。MyISAM的併發插入特性允許同時選擇和插入數據。

MyISAM表是獨立於操作系統的,這說明可以輕鬆地將其從Windows服務器移植到Linux服務器;每當我們建立一個MyISAM引擎的表時,就會在本地磁盤上建立三個文件,文件名就是表名。 例如我創建了一個【test】表,那麼就會生成以下三個文件:

文件名 說明
test.frm 存儲表定義
test.MYD 存儲數據
test.MYI 存儲索引

4. InnoDB引擎

InnoDB表類型可以看作是對MyISAM的進一步更新產品,它提供了事務、行級鎖機制和外鍵約束的功能。InnoDB的表需要更多的內存和存儲,它會在主內存中建立其專用的緩衝池用於高速緩衝數據和索引。 使用InnoDB是最理想的選擇:

  • 更新密集的表:InnoDB存儲引擎特別適合處理多重併發的更新請求

  • 事務:InnoDB存儲引擎是支持事務的標準MySQL存儲引擎

  • 自動災難恢復:與其它存儲引擎不同,InnoDB表能夠自動從災難中恢復

  • 外鍵約束:MySQL支持外鍵的存儲引擎只有InnoDB

  • 支持自動增加列AUTO_INCREMENT屬性

5. 總結

InnoDB:支持事務處理,支持外鍵,支持崩潰修復能力和併發控制。如果需要對事務的完整性要求比較高(比如銀行),要求實現併發控制(比如售票),那選擇InnoDB有很大的優勢。如果需要頻繁的更新、刪除操作的數據庫,也可以選擇InnoDB,因爲支持事務的提交(commit)和回滾(rollback)。

MyISAM:插入數據快,空間和內存使用比較低。如果表主要是用於插入新記錄和讀出記錄,那麼選擇MyISAM能實現處理高效率。如果應用的完整性、併發性要求比較低,也可以使用。

注意,同一個數據庫也可以使用多種存儲引擎的表。如果一個表要求比較高的事務處理,可以選擇InnoDB。這個數據庫中可以將查詢要求比較高的表選擇MyISAM存儲。如果該數據庫需要一個用於查詢的臨時表,可以選擇MEMORY存儲引擎。

 

二. 索引結構(方法、算法)

在mysql中常用兩種索引結構(算法)BTree和Hash,兩種算法檢索方式不一樣,對查詢的作用也不一樣。

常用存儲引擎對應的索引結構

存儲引擎 顯示支持索引結構
InnoDB BTREE
MyISAM BTREE
MEMORY/HEAP HASH,BTREE
NDB HASH, BTREE (see note in text)

mysql InnoDB存儲引擎 是支持hash索引的,不過,我們必須啓用,hash索引的創建由InnoDB存儲引擎引擎自動優化創建,我們干預不了。

 

1.Hash

Hash索引的底層實現是由Hash表來實現的,非常適合以 key-value 的形式查詢,也就是單個key 查詢,或者說是等值查詢。其結構如下所示:

 

從上面結構可以看出,Hash 索引可以比較方便的提供等值查詢的場景,由於是一次定位數據,不像BTree索引需 要從根節點到枝節點,最後才能訪問到頁節點這樣多次IO訪問,所以檢索效率遠高於BTree索引。但是對於範圍查詢的話,就需要進行全表掃描了。

但爲什麼我們使用BTree比使用Hash多呢?主要Hash本身由於其特殊性,也帶來了很多限制和弊端:

  1. Hash索引僅僅能滿足“=”,“IN”,“<=>”查詢,不能使用範圍查詢。

  2. 聯合索引中,Hash索引不能利用部分索引鍵查詢。 對於聯合索引中的多個列,Hash是要麼全部使用,要麼全部不使用,並不支持BTree支持的聯合索引的最優前綴,也就是聯合索引的前面一個或幾個索引鍵進行查詢時,Hash索引無法被利用。

  3. Hash索引無法避免數據的排序操作 由於Hash索引中存放的是經過Hash計算之後的Hash值,而且Hash值的大小關係並不一定和Hash運算前的鍵值完全一樣,所以數據庫無法利用索引的數據來避免任何排序運算。

  4. Hash索引任何時候都不能避免表掃描 Hash索引是將索引鍵通過Hash運算之後,將Hash運算結果的Hash值和所對應的行指針信息存放於一個Hash表中,由於不同索引鍵存在相同Hash值,所以即使滿足某個Hash鍵值的數據的記錄條數,也無法從Hash索引中直接完成查詢,還是要通過訪問表中的實際數據進行比較,並得到相應的結果。

  5. Hash索引遇到大量Hash值相等的情況後性能並不一定會比BTree高 對於選擇性比較低的索引鍵,如果創建Hash索引,那麼將會存在大量記錄指針信息存於同一個Hash值相關聯。這樣要定位某一條記錄時就會非常麻煩,會浪費多次表數據訪問,而造成整體性能底下。

2. B+Tree

B+Tree索引是最常用的mysql數據庫索引算法,因爲它不僅可以被用在=,>,>=,<,<=和between這些比較操作符上,而且還可以用於like操作符,只要它的查詢條件是一個不以通配符開頭的常量,例如:* select * from user where name like 'jack%'; select * from user where name like 'jac%k%'; 如果一通配符開頭,或者沒有使用常量,則不會使用索引,例如: select * from user where name like '%jack'; select * from user where name like simply_name;

3. 擴展:B+/-Tree原理

B樹和B+樹 B樹和B+樹算是數據結構中出現頻率十分高的模型了,在筆者之前的幾篇博客,有對二叉查找樹和二叉平衡樹進行過講解和代碼分析,但是那些都是在程序中使用比較多的樹,在數據庫中,數據量相對較大,多路查找樹顯然更加適合數據庫的應用場景,接下來我們就介紹這兩類多路查找樹,畢竟作爲程序員,心裏沒點B樹怎麼能行呢?

B樹:B樹就是B-樹,他有着如下的特性:

1、B樹不同於二叉樹,他們的一個節點可以存儲多個關鍵字和多個子樹指針,這也是B+樹的特點;

2、一個m階的B樹要求除了根節點以外,所有的非葉子子節點必須要有[m/2,m]個子樹;

3、根節點必須只能有兩個子樹,當然,如果只有根節點一個節點的情況存在;

4、B樹是一個查找二叉樹,這點和二叉查找樹很像,他都是越靠前的子樹越小,並且,同一個節點內,關鍵字按照大小排序;

5、B樹的一個節點要求子樹的個數等於關鍵字的個數+1;

好了,話不多說,看看B樹的模型吧:

 

由於B樹將所有的查找關鍵字都放在節點中,所以查找方式和二叉查找十分相像,比如說查找E:

先通過根節點找到了左子樹,再順序地遍歷左子樹,發現E在F和J的中間,於是查找葉子節點,順序遍歷關鍵字以後就可以返回E了,如果未能查到E,則表示沒有找到。

B+樹 人人都喜歡plus,B+樹就是這麼一個plus,後頭所講解的索引,就是用的B+樹,我們先來看看他的特性吧:

1、B+樹將所有的查找結果放在葉子節點中,這也就意味着查找B+樹,就必須到葉子節點才能返回結果;

2、B+樹每一個節點的關鍵字個數和子樹指針個數相同;

3、B+樹的非葉子節點的每一個關鍵字對應一個指針,而關鍵字則是子樹的最大,或者最小值;

看看模型吧:

 

一個3階的B+樹

他的查找方式也是簡單粗暴的,和B樹十分像,只不過他會在葉子節點中找到目標,比如我們找兔:

第一步比馬小,就會查找他的子樹,第二部比龍小,就會查找他的子樹,最後在葉子節點中的關鍵字命中目標。

那麼MySql是如何利用這數據結構的呢?

 

三. 索引方式

Mysql數據庫中的B+樹索引可以分爲聚集索引和非聚集索引(輔助索引)

聚集索引

聚集索引:指索引項的排序方式和表中數據記錄排序方式一致的索引(這裏不懂先放着,一會舉例),每張表只能有一個聚集索引,聚集索引的葉子節點存儲了整個行數據(即:一張表只能有一個聚集索引)。

解釋:什麼叫索引項的排序方式和表中數據記錄排序方式一致呢? 我們把一本字典看做是數據庫的表,那麼字典的拼音目錄就是聚集索引,它按照A-Z排列。實際存儲的字也是按A-Z排列的。這就是索引項的排序方式和表中數據記錄排序方式一致。

對於Innodb,主鍵毫無疑問是一個聚集索引。但是當一個表沒有主鍵,或者沒有一個索引,Innodb會如何處理呢。請看如下規則:

  • 如果一個主鍵被定義了,那麼這個主鍵就是作爲聚集索引。

  • 如果沒有主鍵被定義,那麼該表的第一個唯一非空索引被作爲聚集索引。

  • 如果沒有主鍵也沒有合適的唯一索引,那麼innodb內部會生成一個隱藏的主鍵作爲聚集索引,這個隱藏的主鍵是一個6個字節的列,該列的值會隨着數據的插入自增。

非聚集索引

非聚集索引:非聚集索引中索引的邏輯順序與磁盤上行的物理存儲順序不同,一個表中可以擁有多個非聚集索引。葉子節點並不包含行記錄的全部數據。葉子節點除了包含鍵值以外,還存儲了一個指向改行數據的聚集索引建的書籤。

四. 索引類型

MySQL目前主要有以下幾種索引類型:

  • 普通索引

  • 唯一索引

  • 主鍵索引

  • 組合索引

  • 全文索引

 

普通索引

這是最基本的索引,它沒有任何限制。它有以下幾種創建方式:

(1)直接創建索引

CREATE INDEX index_name ON table(column(length))  

(2)修改表結構的方式添加索引

ALTER TABLE table_name ADD INDEX index_name ON (column(length))

(3)創建表的時候同時創建索引

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`),
    INDEX index_name (title(length))
)

唯一索引

與前面的普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值。如果是組合索引,則列值的組合必須唯一。它有以下幾種創建方式:

(1)創建唯一索引

CREATE UNIQUE INDEX indexName ON table(column(length))

(2)修改表結構

ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))

(3)創建表的時候直接指定

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    UNIQUE indexName (title(length))
);

主鍵索引

是一種特殊的唯一索引,一個表只能有一個主鍵,不允許有空值。一般是在建表的時候同時創建主鍵索引:

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) NOT NULL ,
    PRIMARY KEY (`id`)
);

組合索引

指多個字段上創建的索引,只有在查詢條件中使用了創建索引時的第一個字段,索引纔會被使用。使用組合索引時遵循最左前綴集合

ALTER TABLE `table` ADD INDEX name_city_age (name,city,age); 

全文索引

主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個搜索引擎,而不是簡單的where語句的參數匹配。fulltext索引配合match against操作使用,而不是一般的where語句加like。它可以在create table,alter table ,create index使用,不過目前只有char、varchar,text 列上可以創建全文索引。值得一提的是,在數據量較大時候,現將數據放入一個沒有全局索引的表中,然後再用CREATE index創建fulltext索引,要比先爲一張表建立fulltext然後再將數據寫入的速度快很多。

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`),
    FULLTEXT (content)
);

 

五. 索引原理以及結構

1. MyISAM索引實現

MyISAM引擎使用B+Tree作爲索引結構,葉子節點中存儲數據行的物理地址(data域存放的是數據記錄的物理地址)。下圖是MyISAM索引的原理圖:

 

 

這裏設表一共有三列,假設我們以Col1爲主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:

 

 

 

同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然後以data域的值爲地址,讀取相應數據記錄。

MyISAM的索引方式也叫做“非聚集”的,之所以這麼稱呼是爲了與InnoDB的聚集索引區分。

2. InnoDB索引實現

Innodb使用的是B+樹,他存在有一個主鍵索引(聚集索引)和輔助索引(非聚集索引)兩種索引,主鍵索引是在生成主鍵時就有的索引,他的葉子節點中存放的就是數據行,所以又稱之爲聚集索引。

而另一類索引,輔助索引,就是我們人爲新建的索引,他的葉子節點中存放的是主鍵,當我們通過輔助索引查找到主鍵之後,再通過查找的主鍵去查找主鍵索引(所以非聚集索引有二次查詢的問題,即 回表 解決方式)。

雖然InnoDB也使用B+Tree作爲索引結構,但具體實現方式卻與MyISAM截然不同。

A. 第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

 

圖10

圖10是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形。

B. 第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作爲data域。例如,圖11爲定義在Col3上的一個輔助索引:

 

圖11

這裏以英文字符的ASCII碼作爲比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

爲了更準確直觀的瞭解 聚集索引與非聚集索引的 關係。我們看一個主鍵索引與普通索引的案例

CREATE TABLE `t_user` (
  `id` bigint(20) NOT NULL COMMENT '主鍵ID',
  `age` int(10) DEFAULT NULL COMMENT '年齡',
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
​
INSERT INTO `t_user` VALUES ('100', '10'), ('200', '20'), ('300', '30'), ('500', '50'), ('600', '60');

上面表和數據的存儲結構大致如下所示:

 

從上圖可以看出,有 2 個索引結構:主鍵ID 索引和普通索引。主鍵索引的葉子節點存儲的是行數據的內容(聚簇索引),普通索引的葉子節點存儲的是主鍵的值(非聚簇索引/二級索引)。

 

瞭解不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現後,就很容易明白爲什麼不建議使用過長的字段作爲主鍵,因爲所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作爲主鍵在InnoDB中不是個好主意,因爲InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作爲主鍵則是一個很好的選擇

3.組合索引原理及存儲結構

上面我們已經看了MyISAM和InnoDB索引的例子,但講述的都是單列索引,聯合索引對應的結構圖是怎樣的

 

我們創建索引時,也會經常創建如 idx_name_age (name, age) 這樣的索引結構。並且還知道 WHERE 條件中 name = ? AND age = ? 和 name = ? 都可以使用到這個聯合索引。下面我們來看一下其結構,看一下爲什麼是可以做到這一點的。

 

 

疑惑:聯合索引的結構是怎樣的

比方說聯合索引 (col1, col2,col3),我知道在邏輯上是先按照col1進行排序再按照col2進行排序最後再按照col3進行排序。

其相當於創建了三個索引:(col1)、(col1,col2)、(col1,col2,col3)

因此如果是select * from table where col1 = 1 and col3 = 3的話,只有col1的索引部分能生效。

但是其物理結構上這個聯合索引是怎樣存在的,我想不懂。

解答:聯合索引的結構

上網查閱了許多資料,總算有點眉目了。

假設這是一個多列索引(col1, col2,col3),對於葉子節點,是這樣的:

 

 

PS:該圖改自《MySQL索引背後的數據結構及算法原理》一文的配圖。

也就是說,聯合索引(col1, col2,col3)也是一棵B+Tree,其非葉子節點存儲的是第一個關鍵字的索引,而葉節點存儲的則是三個關鍵字col1、col2、col3三個關鍵字的數據,且按照col1、col2、col3的順序進行排序(注意:子葉節點的data存儲的是主鍵id,參見非聚集索引)。

配圖可能不太讓人滿意,因爲col1都是不同的,也就是說在col1就已經能確定結果了。自己又畫了一個圖(有點醜),col1表示的是年齡,col2表示的是姓氏,col3表示的是名字。如下圖:

 

 

 

PS:對應地址指的是數據記錄的地址。

如圖,聯合索引(年齡, 姓氏,名字),葉節點上data域存儲的是三個關鍵字的數據。且是按照年齡、姓氏、名字的順序排列的。

因此,如果執行的是: select * from STUDENT where 姓氏='李' and 名字='安'; 或者 select * from STUDENT where 名字='安'; 那麼當執行查詢的時候,是無法使用這個聯合索引的。因爲聯合索引中是先根據年齡進行排序的。如果年齡沒有先確定,直接對姓氏和名字進行查詢的話,就相當於亂序查詢一樣,因此索引無法生效。因此查詢是全表查詢。

如果執行的是: select * from STUDENT where 年齡=1 and 姓氏='李'; 那麼當執行查詢的時候,索引是能生效的,從圖中很直觀的看出,age=1的是第一個葉子節點的前6條記錄,在age=1的前提下,姓氏=’李’的是前3條。因此最終查詢出來的是這三條,從而能獲取到對應記錄的地址。 如果執行的是: select * from STUDENT where 年齡=1 and 姓氏='黃' and 名字='安'; 那麼索引也是生效的。

而如果執行的是: select * from STUDENT where 年齡=1 and 名字='安'; 那麼,索引年齡部分能生效,名字部分不能生效。也就是說索引部分生效。

因此我對聯合索引結構的理解就是B+Tree是按照第一個關鍵字進行索引,然後在葉子節點上按照第一個關鍵字、第二個關鍵字、第三個關鍵字…進行排序。

最左原則

而之所以會有最左原則,是因爲聯合索引的B+Tree是按照第一個關鍵字進行索引排列的。

索引存儲的值按索引列中的順序排列。可以利用B-Tree索引進行全關鍵字、關鍵字範圍和關鍵字前綴查詢,當然,如果想使用索引,你必須保證按索引的最左邊前綴(leftmost prefix of the index)來進行查詢。 (1)匹配全值(Match the full value):對索引中的所有列都指定具體的值。例如,上圖中索引可以幫助你查找18歲的李安 (2)匹配最左前綴(Match a leftmost prefix):你可以利用索引查找年齡爲21的人,僅僅使用索引中的第1列。 (3)匹配列前綴(Match a column prefix):例如,你可以利用索引查找last name以J開始的人,這僅僅使用索引中的第1列。 (4)匹配值的範圍查詢(Match a range of values):可以利用索引查找年齡在21到30之間的人,僅僅使用索引中第1列。 (5)匹配部分精確而其它部分進行範圍匹配(Match one part exactly and match a range on another part):可以利用索引查找年齡爲21,而姓氏以字母K開始的人。 (6)僅對索引進行查詢(Index-only queries):如果查詢的列都位於索引中,則不需要讀取元組的值。 由於B-樹中的節點都是順序存儲的,所以可以利用索引進行查找(找某些值),也可以對查詢結果進行ORDER BY。當然,使用B-tree索引有以下一些限制: (1) 查詢必須從索引的最左邊的列開始。關於這點已經提了很多遍了。例如你不能利用索引查找在某一天出生的人。(2) 不能跳過某一索引列。例如,你不能利用索引查找last name爲Smith且出生於某一天的人。 (3) 存儲引擎不能使用索引中範圍條件右邊的列。例如,如果你的查詢語句爲WHERE age=21 AND first_name LIKE '李%' AND Name='安',則該查詢只會使用索引中的前兩列,因爲LIKE是範圍查詢。

 

六. 索引優化

mysql執行計劃中的extra列中表明瞭執行計劃的每一步中的實現細節,其中包含了與索引相關的一些細節信息 其中跟索引有關的using index 在不同的情況下會出現Using index, Using where Using index ,Using index condition等 那麼Using index 和 Using where;Using index 有什麼區別?網上搜了一大把文章,說實在話也沒怎麼弄懂,於是就自己動手試試。

本文僅從最簡單的單表去測試using index 和 using where using index以及簡單測試using index condition的情況的出現時機 。 執行計劃的生成與表結構,表數據量,索引結構,統計信息等等上下文等多種環境有關,無法一概而論,複雜情況另論。

測試環境搭建

  測試表以及測試數據搭建,類似於訂單表和訂單明細表,暫時先用訂單表做測試

  測試表結構

create table test_order
(
    id int auto_increment primary key,
    user_id int,
    order_id int,
    order_status tinyint,
    create_date datetime
);
create table test_orderdetail
(
    id int auto_increment primary key,
    order_id int,
    product_name varchar(100),
    cnt int,
    create_date datetime
);
create index idx_userid_order_id_createdate on test_order(user_id,order_id,create_date);
create index idx_orderid_productname on test_orderdetail(order_id,product_name);

創建存儲過程(insertOrder),創建測試數據,腳本如下

BEGIN
	 declare v_uuid  varchar(50);
DECLARE  v_orderId int;
    while loopcount>0 do
        set v_uuid = uuid();
				SET v_orderId=rand()*100000;
        insert into test_order (user_id,order_id,order_status,create_date) values (rand()*1000,v_orderId,rand()*10,DATE_ADD(NOW(), INTERVAL - RAND()*20000 HOUR));
        insert into test_orderdetail(order_id,product_name,cnt,create_date) values (v_orderId,v_uuid,rand()*10,DATE_ADD(NOW(), INTERVAL - RAND()*20000 HOUR));
        set loopcount = loopcount -1;
    end while;
END

創建50W條測試數據

call insertOrder (50000);

Using index

1,查詢的列被索引覆蓋,並且where篩選條件是索引的是前導列,Extra中爲Using index

-- 查詢的列全部在索引中,並且where篩選條件是索引的前導列
-- type:ref(索引查找)+ Extra:using index
EXPLAIN SELECT user_id,order_id,create_date from test_order where user_id=1;

 

Using where Using index

  1. 查詢的列被索引覆蓋,並且where篩選條件是索引列之一但是不是索引的不是前導列,Extra中爲Using where; Using index,意味着無法直接通過索引查找來查詢到符合條件的數據

-- 查詢的列全部在索引中,並且where的篩選條件不符合索引的前導列
-- type:index(索引掃描) + Extra:Using where; Using index
EXPLAIN SELECT user_id,order_id,create_date from test_order where order_id=67260;

 

  1. 查詢的列被索引覆蓋,並且where篩選條件是索引列前導列的一個範圍,同樣意味着無法直接通過索引查找查詢到符合條件的數據

-- 查詢的列全部在索引中,並且where的篩選條件是索引前導列的一個範圍
-- type:index(索引掃描) + Extra:Using where; Using index

EXPLAIN SELECT user_id,order_id,create_date from test_order where user_id<5;

 

 

NULL(既沒有Using index,也沒有Using where Using index,也沒有using where)

 1,查詢的列未被索引覆蓋,並且where篩選條件是索引的前導列,    意味着用到了索引,但是部分字段未被索引覆蓋,必須通過“回表”來實現,不是純粹地用到了索引,也不是完全沒用到索引,Extra中爲NULL(沒有信息)

-- 查詢的列不全在索引中,並且where的篩選條件是索引的前導列
-- type:ref + Extra:NULL
EXPLAIN SELECT * from test_order where user_id=1;

 

Using where

   查詢的列未被索引覆蓋,where篩選條件非索引的前導列,Extra中爲Using where

-- 查詢的列不全在索引中,並且where的篩選條件不是索引的前導列
EXPLAIN SELECT * from test_order where order_id=67260;

using where 意味着通過索引或者表掃描的方式進程where條件的過濾, 反過來說,也就是沒有可用的索引查找,當然這裏也要考慮索引掃描+回表與表掃描的代價。 這裏的type都是all,說明MySQL認爲全表掃描是一種比較低的代價。

Using index condition

  1. 查詢的列不全在索引中,where條件中是一個前導列的範圍

-- 查詢的列不全在索引中,where條件中是一個前導列的範圍
EXPLAIN SELECT * from test_order where user_id>1 and user_id<10;

 

  1. 查詢列不完全被索引覆蓋,查詢條件完全可以使用到索引(進行索引查找)

    -- 查詢的列不全在索引中,查詢條件完全可以使用到索引(進行索引查找)
    EXPLAIN SELECT * from test_order where user_id=1 and order_id=67260 and create_date>'2017-03-15';
    

 

 

結論:

  1. Extra中的爲Using index的情況 where篩選列是索引的前導列 &&查詢列被索引覆蓋 && where篩選條件是一個基於索引前導列的查詢,意味着通過索引超找就能直接找到符合條件的數據,並且無須回表

  2. Extra中的爲空的情況   查詢列存在未被索引覆蓋&&where篩選列是索引的前導列,意味着通過索引超找並且通過“回表”來找到未被索引覆蓋的字段,

  3. Extra中的爲Using where Using index: 出現Using where Using index意味着是通過索引掃描(或者表掃描)來實現sql語句執行的,即便是索引前導列的索引範圍查找也有一點範圍掃描的動作,不管是前非索引前導列引起的,還是非索引列查詢引起的。

 MySQL執行計劃中的Extra中信息非常多,不僅僅包括Using index,Using where Using index,Using index condition,Using where,尤其是在多表連接的時候,這一點在相對MSSQL來說,不夠直觀或者結構化。   MSSQL中是通過區分索引查找(index seek),索引掃描(index scan),表掃描(table scan)來實現具體的查詢的,這圖形化的執行計劃在不同的場景下是非常直觀的,要想完全弄懂MySQL的這個執行計劃,可能要更多地在實踐中摸索。

 

  1. id 表示執行的順序,id越大越先執行,id一樣的從上往下執行。

  2. select_type

  3. table表名或者表的別名。

  4. partitions分區信息,非分區表爲null。

  5. type 訪問類型,表示找到所查詢數據的方法,也是本文重點介紹的屬性。該屬性的常見值如下,性能從好到差:

  • NULL:無需訪問表或者索引,比如獲取一個索引列的最大值或最小值。

  • system/const:當查詢最多匹配一行時,常出現於where條件是=的情況。system是const的一種特殊情況,既表本身只有一行數據的情況。

  • eq_ref: 每次與之前的表合併行都只在該表讀取一行,這是除了system,const之外最好的一種,特點是使用=,而且索引的所有部分都參與join且索引是主鍵或非空唯一鍵的索引

  • ref: 如果每次只匹配少數行,那就是比較好的一種,使用=或<=>,可以是左覆蓋索引或非主鍵或非唯一鍵

  • fulltext: 全文搜索

  • ref_or_null: 與ref類似,但包括NULL

  • index_merge 表示出現了索引合併優化(包括交集,並集以及交集之間的並集),但不包括跨表和全文索引。 這個比較複雜,目前的理解是合併單表的範圍索引掃描(如果成本估算比普通的range要更優的話)

  • unique_subquery 在in子查詢中,就是value in (select...)把形如“select unique_key_column”的子查詢替換。PS:所以不一定in子句中使用子查詢就是低效的!

  • index_subquery 同上,但把形如”select non_unique_key_column“的子查詢替換

  • range 常數值的範圍(索引範圍掃描),對索引的掃描開始於某一點,返回匹配值域的行,常見於between、<、>等的查詢

  • index a.當查詢是索引覆蓋的,即所有數據均可從索引樹獲取的時候(Extra中有UsingIndex); b.以索引順序從索引中查找數據行的全表掃描(無 UsingIndex); c.如果Extra中Using Index與Using Where同時出現的話,則是利用索引查找鍵值的意思; d.如單獨出現,則是用讀索引來代替讀行,但不用於查找

  • all 遍歷全表以找到匹配的行

  • null:MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引

possible_keys 表示mysql此次查詢中可能使用的索引。

key 表示mysql實際在此次查詢中使用的索引。

key_len 表示mysql使用的索引的長度。該值越小越好。

ref 表示連接查詢的連接條件。

rows 表示mysql估計此次查詢所需讀取的行數。該值越小越好。

extra 表示mysql解決查詢的其他信息,有幾十種不同的值,該信息也是我們優化sql可以專注的一個值。關於這個extra信息我可能會再下一篇中介紹,這裏先略過。

 

索引結果值從好到壞依次是:

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

優化原則

禁用select *
使用select count(*) 統計行數
儘量少運算
儘量避免全表掃描,如果可以,在過濾列建立索引
儘量避免在where子句對字段進行null判斷
儘量避免在where子句使用!= 或者<>
儘量避免在where子句使用or連接
儘量避免對字段進行表達式計算
儘量避免對字段進行函數操作
儘量避免使用不是複合索引的前綴列進行過濾連接
儘量少排序,如果可以,建立索引
儘量少join
儘量用join代替子查詢
儘量避免在where子句中使用in,not in或者having,使用exists,not exists代替
儘量避免兩端模糊匹配 like %***%
儘量用union all代替union
儘量早過濾
避免類型轉換
儘量批量insert
優先優化高併發sql,而不是頻率低的大sql
儘可能對每一條sql進行explain
儘可能從全局出發

 


七. 總結

八. 擴展:常見問題

1.如何解決非聚集索引二次查詢(回表)的問題?

建立兩列以上的索引,即可查詢複合索引裏的列的數據而不需要進行回表二次查詢,如index(col1, col2),執行下面的語句:

select col1, col2 from t1 where col1 = '213';

因爲複合索引的列包括了col1和col2,不需要查詢別的列,所以不需要進行二次查詢。

要注意使用複合索引需要滿足最左側索引的原則,也就是查詢的時候如果where條件裏面沒有最左邊的一到多列,索引就不會起作用。

 

索引使用注意事項:

1.索引會忽略null值,所以我們在設計數據庫的時候設置爲爲NOT NULL;

2.短索引(給索引設置長度)不僅能夠提高查詢速度,而且能節省I/O操作。

  1. Mysql在查詢的時候只會使用一個索引,但不是一個字段

  2. 不鼓勵使用like對索引操作:like"%aaa%"不會使用索引;但like“aaa%”會使用索引。即可以對固定起始值模糊查詢

5.不適用於否地操作(not in , <>, !=,or) //用到or地方,儘量用union,或者程序兩次查找

6.如果創建了索引,但是查詢語句並沒有使用,則會使原來的效率更差

 

發佈了38 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章