索引類型
索引的建立對于MySQL的高效運行是很重要的,索引可以大大提高MySQL的檢索速度。mysql索引大致可以分為普通索引、唯一索引、主鍵索引、組合索引、全文索引,下面我們就來具體了解下各個索引的區別:
普通索引
基本的索引類型,值可以為空,沒有唯一性的限制。
- 直接創建索引
CREATE INDEX index_name ON table_name(col_name);
-- 修改表結構的方式添加索引
ALTER TABLE table_name ADD INDEX index_name(col_name);
-- 創建表的時候同時創建索引
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) NOT NULL ,
`age` int(4) NULL ,
PRIMARY KEY (`id`),
INDEX index_name (age(4))
)
-- 刪除索引
DROP INDEX index_name ON table_name;
-- 或
alter table `表名` drop index 索引名;
唯一索引
索引列中的值必須是唯一的,但是允許為空值(只允許存在一條空值)。
-- 創建單個索引
CREATE UNIQUE INDEX index_name ON table_name(col_name);
-- 創建多個索引
CREATE UNIQUE INDEX index_name on table_name(col_name,...);
-- 修改表結構
-- 單個
ALTER TABLE table_name ADD UNIQUE index index_name(col_name);
-- 多個
ALTER TABLE table_name ADD UNIQUE index index_name(col_name,...);
主鍵索引
主鍵是一種唯一性索引,但它必須指定為PRIMARY KEY
,每個表只能有一個主鍵。
-- 主鍵索引(創建表時添加)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) NOT NULL ,
`age` int(4) NULL ,
PRIMARY KEY (`id`)
)
-- 主鍵索引(創建表后添加)
CREATE TABLE `order` (
`orderId` varchar(36) NOT NULL,
`productId` varchar(36) NOT NULL ,
`time` varchar(20) NULL DEFAULT NULL
)
alter table `order` add primary key(`orderId`);
組合索引
在多個字段上創建的索引。組合索引遵守“最左前綴”原則,即在查詢條件中使用了復合索引的第一個字段,索引才會被使用。因此,在復合索引中索引列的順序至關重要。
-- 創建一個復合索引
create index index_name on table_name(col_name1,col_name2,...);
-- 修改表結構的方式添加索引
alter table table_name add index index_name(col_name,col_name2,...);
全文索引
全文索引的索引類型為FULLTEXT
。全文索引可以在varchar、char、text
類型的列上創建。可以通過ALTER TABLE
或CREATE INDEX
命令創建。對于大規模的數據集,通過ALTER TABLE
(或CREATE INDEX
)命令創建全文索引要比把記錄插入帶有全文索引的空表更快。MyISAM
支持全文索引,InnoDB
在mysql5.6之后支持了全文索引
。 全文索引不支持中文
需要借sphinx(coreseek)
或迅搜<、code>技術處理中文。
-- 創建表的適合添加全文索引
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` varchar(255) NOT NULL ,
`content` text NOT NULL ,
`time` varchar(20) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
)
-- 修改表結構添加全文索引
ALTER TABLE table_name ADD FULLTEXT index_fulltext_content(col_name);
索引實現
下面我們來具體看下載不同的數據庫引擎下上述索引的實現原理:
innodb索引
主鍵索引
每個InnoDB表都有一個主鍵索引(也叫聚簇索引) ,聚簇索引使用B+樹構建,葉子節點存儲的數據是整行記錄。。InnoDB創建索引的具體規則如下:
在表上定義主鍵PRIMARY KEY,InnoDB將主鍵索引用作聚簇索引
如果表沒有定義主鍵,且存在非空unique列,則選擇第一個非空unique列為主鍵,構建聚簇索引;
如果以上兩個都沒有,InnoDB 會使用一個6 字節長整型的隱式字段 ROWID字段構建聚簇索引。該ROWID字段會在插入新行時自動遞增
除聚簇索引之外的所有索引都稱為輔助索引。在中InnoDB,輔助索引中的葉子節點存儲的數據是該行的主鍵值。 在檢索時,InnoDB使用此主鍵值在聚簇索引中搜索行記錄。 主鍵索引的葉子節點會存儲數據行(所以一般進行的非主鍵索引檢索至少需要經歷兩次磁盤IO),輔助索引只會存儲主鍵值。具體主鍵索引存儲結構如下:
等值查詢數據
先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較28<75,走左路。(1次磁盤IO)
將左子樹節點加載到內存中,比較16<28<47,向下檢索。(1次磁盤IO)
檢索到葉節點,將節點加載到內存中遍歷,比較16<28,18<28,28=28。查找到值等于28的索引項,直接可以獲取整行數據。將改記錄返回給客戶端。(1次磁盤IO)
磁盤IO數量:3次。
輔助索引
除聚簇索引之外的所有索引都稱為輔助索引,InnoDB的輔助索引只會存儲主鍵值而非磁盤地址。以表user的age列為例,age索引的索引結果如下圖:
底層葉子節點的按照(age,id)的順序排序,先按照age列從小到大排序,age列相同時按照id列從小到大排序。使用輔助索引需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后使用主鍵到主索引中檢索獲得記錄。
場景一:等值查詢的情況
select * from t_user_innodb where age=19;
根據在輔助索引樹中獲取的主鍵id,到主鍵索引樹檢索數據的過程稱為回表查詢。
磁盤IO數:輔助索引3次+獲取記錄回表3次
組合索引
還是以自己創建的一個表為例:表 abc_innodb,id為主鍵索引,創建了一個聯合索引idx_abc(a,b,c)。
CREATE TABLE `abc_innodb`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` varchar(10) DEFAULT NULL,
`d` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_abc` (`a`, `b`, `c`)
) ENGINE = InnoDB;
組合索引的數據結構:
組合索引的查詢過程:
select * from abc_innodb where a = 13 and b = 16 and c = 4;
最左匹配原則
最左前綴匹配原則和聯合索引的索引存儲結構和檢索方式是有關系的。
在組合索引樹中,最底層的葉子節點按照第一列a列從左到右遞增排列,但是b列和c列是無序的,b列只有在a列值相等的情況下小范圍內遞增有序,而c列只能在a,b兩列相等的情況下小范圍內遞增有序。
就像上面的查詢,B+樹會先比較a列來確定下一步應該搜索的方向,往左還是往右。如果a列相同再比較b列。但是如果查詢條件沒有a列,B+樹就不知道第一步應該從哪個節點查起。
可以說創建的idx_abc(a,b,c)索引,相當于創建了(a)、(a,b)(a,b,c)三個索引。、
組合索引的最左前綴匹配原則:使用組合索引查詢時,mysql會一直向右匹配直至遇到范圍查詢(>、<、between、like)就停止匹配。
覆蓋索引
覆蓋索引并不是說是索引結構,覆蓋索引是一種很常用的優化手段。因為在使用輔助索引的時候,我們只可以拿到主鍵值,相當于獲取數據還需要再根據主鍵查詢主鍵索引再獲取到數據。但是試想下這么一種情況,在上面abc_innodb表中的組合索引查詢時,如果我只需要abc字段的,那是不是意味著我們查詢到組合索引的葉子節點就可以直接返回了,而不需要回表。這種情況就是覆蓋索引。可以看一下執行計劃:
MyIsam索引
MyISAM的數據文件和索引文件是分開存儲的。MyISAM使用B+樹構建索引樹時,葉子節點中存儲的鍵值為索引列的值,數據為索引所在行的磁盤地址。
主鍵索引
表user的索引存儲在索引文件user.MYI
中,數據文件存儲在數據文件 user.MYD
中。簡單分析下查詢時的磁盤IO情況:
場景一:根據主鍵等值查詢數據
先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較28<75,走左路。(1次磁盤IO)
將左子樹節點加載到內存中,比較16<28<47,向下檢索。(1次磁盤IO)
檢索到葉節點,將節點加載到內存中遍歷,比較16<28,18<28,28=28。查找到值等于30的索引項。(1次磁盤IO)
從索引項中獲取磁盤地址,然后到數據文件user.MYD中獲取對應整行記錄。(1次磁盤IO)
將記錄返給客戶端。
磁盤IO次數:3次索引檢索+記錄數據檢索。
場景二:根據主鍵范圍查詢數據
先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較28<75,走左路。(1次磁盤IO)
將左子樹節點加載到內存中,比較16<28<47,向下檢索。(1次磁盤IO)
-
檢索到葉節點,將節點加載到內存中遍歷比較16<28,18<28,28=28<47。查找到值等于28的索引項。
根據磁盤地址從數據文件中獲取行記錄緩存到結果集中。(1次磁盤IO)
我們的查詢語句時范圍查找,需要向后遍歷底層葉子鏈表,直至到達最后一個不滿足篩選條件。
向后遍歷底層葉子鏈表,將下一個節點加載到內存中,遍歷比較,28<47=47,根據磁盤地址從數據文件中獲取行記錄緩存到結果集中。(1次磁盤IO)
最后得到兩條符合篩選條件,將查詢結果集返給客戶端。
磁盤IO次數:4次索引檢索+記錄數據檢索。
備注:以上分析僅供參考,MyISAM在查詢時,會將索引節點緩存在MySQL緩存中,而數據緩存依賴于操作系統自身的緩存,所以并不是每次都是走的磁盤,這里只是為了分析索引的使用過程。
輔助索引
在 MyISAM 中,輔助索引和主鍵索引的結構是一樣的,沒有任何區別,葉子節點的數據存儲的都是行記錄的磁盤地址。只是主鍵索引的鍵值是唯一的,而輔助索引的鍵值可以重復。查詢數據時,由于輔助索引的鍵值不唯一,可能存在多個擁有相同的記錄,所以即使是等值查詢,也需要按照范圍查詢的方式在輔助索引樹中檢索數據。
索引失效情況
情況一:where語句中包含or時,可能會導致索引失效
使用or并不是一定會使索引失效,你需要看or左右兩邊的查詢列是否命中相同的索引。
-- 假設user表中的user_id列有索引,age列沒有索引
-- 能命中索引
select * from user where user_id = 1 or user_id = 2;
-- 無法命中索引
select * from user where user_id = 1 or age = 20;
-- 假設age列也有索引的話,依然是無法命中索引的
select * from user where user_id = 1 or age = 20;
可以根據情況盡量使用union all或者in來代替,這兩個語句的執行效率也比or好些。
情況二:where語句中索引列使用了負向查詢,可能會導致索引失效
負向查詢包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。其實負向查詢并不絕對會索引失效,這要看MySQL優化器的判斷,全表掃描或者走索引哪個成本低了。
情況三:索引字段可以為null,使用is null或is not null時,可能會導致索引失效
其實單個索引字段,使用is null或is not null時,是可以命中索引的。
情況四:在索引列上使用內置函數,一定會導致索引失效
比如下面語句中索引列login_time上使用了函數,會索引失效:
select * from user where DATE_ADD(login_time, INTERVAL 1 DAY) = 7;
情況五:隱式類型轉換導致的索引失效
如下面語句中索引列user_id為varchar類型,不會命中索引:
select * from user where user_id = 12;
情況六:對索引列進行運算,一定會導致索引失效
運算如+,-,*,/等,如下:
select * from user where age - 1 = 10;
優化的話,要把運算放在值上,或者在應用程序中直接算好,比如:
select * from user where age = 10 - 1;
情況七:like通配符可能會導致索引失效
like查詢以%開頭時,會導致索引失效。解決辦法有兩種:
- 將%移到后面,如:
select * from user where `name` like '李%';
- 利用覆蓋索引來命中索引:
select name from user where `name` like '%李%';
情況八:MySQL優化器的最終選擇,不走索引
上面有提到,即使完全符合索引生效的場景,考慮到實際數據量等原因,最終是否使用索引還要看MySQL優化器的判斷。當然你也可以在sql語句中寫明強制走某個索引。