原文連接:blog.ouyangsihai.cn >> MySQL的B+樹索引的概念、使用、優化及使用場景 php
這篇文章不會講解索引的基礎知識,主要是關於MySQL數據庫的B+樹索引的相關原理,裏面的一些知識都參考了MySQL技術內幕這本書,也算對於這些知識的總結。對於B樹和B+樹相關的知識,能夠參考個人這篇博客:面試官問你B樹和B+樹,就把這篇文章丟給他html
索引有不少中類型:普通索引、惟一索引、主鍵索引、組合索引、全文索引,下面咱們看看如何建立和刪除下面這些類型的索引。mysql
索引的建立是能夠在不少種狀況下進行的。面試
CREATE [UNIQUE|FULLLTEXT] INDEX index_name ON table_name(column_name(length))
[UNIQUE|FULLLTEXT]
:表示可選擇的索引類型,惟一索引仍是全文索引,不加話就是普通索引。 table_name
:表的名稱,表示爲哪一個表添加索引。 column_name(length)
:column_name是表的列名,length表示爲這一列的前length行記錄添加索引。正則表達式
ALTER TABLE table_name ADD [UNIQUE|FULLLTEXT] INDEX index_name (column(length))
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , PRIMARY KEY (`id`), [UNIQUE|FULLLTEXT] INDEX index_name (title(length)) )
前面講的都是普通索引、惟一索引和全文索引建立的方式,可是,主鍵索引和組合索引建立的方式倒是有點不同的,因此單獨拿出來說一下。算法
組合索引建立方式sql
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , PRIMARY KEY (`id`), INDEX index_name(id,title) )
ALTER TABLE table_name ADD INDEX name_city_age (name,city,age);
主鍵索引建立方式 主鍵索引是一種特殊的惟一索引,一個表只能有一個主鍵,不容許有空值。通常是在建表的時候同時建立主鍵索引。數據庫
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , PRIMARY KEY (`id`) )
刪除索引可利用ALTER TABLE
或DROP INDEX
語句來刪除索引。相似於CREATE INDEX
語句,DROP INDEX
能夠在ALTER TABLE
內部做爲一條語句處理,語法以下。segmentfault
(1)DROP INDEX index_name ON talbe_name
(2)ALTER TABLE table_name DROP INDEX index_name
(3)ALTER TABLE table_name DROP PRIMARY KEY
併發
第3條語句只在刪除PRIMARY KEY
索引時使用,由於一個表只可能有一個PRIMARY KEY
索引,所以不須要指定索引名。
上面講了一下基本的知識,接下來,仍是經過一個具體的例子來體會一下。
create table table_index( id int(11) not null auto_increment, title char(255) not null, primary key(id) );
首先,咱們使用直接添加索引的方式添加一個普通索引。
CREATE INDEX idx_a ON table_index(title);
接着,咱們用修改表結構的時候添加索引。
ALTER TABLE table_index ADD UNIQUE INDEX idx_b (title(100));
最後,咱們再添加一個組合索引。
ALTER TABLE table_index ADD INDEX idx_id_title (id,title);
這樣,咱們就把前面索引的方式都用上一遍了,我相信你也熟悉這些操做了。
SHOW INDEX
命令查看索引信息若是想要查看錶中的索引信息,可使用命令SHOW INDEX
,下面的例子,咱們查看錶table_index
的索引信息。
SHOW INDEX FROM table_index\G;
獲得上面的信息,上面的信息什麼意思呢?咱們逐一介紹!
字段 | 解釋 |
---|---|
Table | 索引所在的表 |
Non_unique | 非惟一索引,若是是0,表明惟一的,也就是說若是該列索引中不包括重複的值則爲0 不然爲1 |
Key_name | 索引的名字,若是是主鍵的話 則爲PRIMARY |
Seq_in_index | 索引中該列的位置,從1開始,若是是組合索引 那麼按照字段在創建索引時的順序排列 |
Collation | 列是以什麼方式存儲在索引中的。能夠是A或者NULL,B+樹索引老是A,排序的, |
Sub_part | 是否列的部分被索引,若是隻是前100行索引,就顯示100,若是是整列,就顯示NULL |
Packed | 關鍵字是否被壓縮,若是沒有,爲NULL |
Index_type | 索引的類型,對於InnoDB只支持B+樹索引,因此都是顯示BTREE |
直接刪除索引方式
DROP INDEX idx_a ON table_index;
修改表結構時刪除索引
ALTER TABLE table_index DROP INDEX idx_b;
在上面介紹了那麼多個關鍵字的意思,可是Cardinality
這個關鍵字很是的關鍵,優化器會根據這個值來判斷是否使用這個索引。在B+樹索引中,只有高選擇性的字段纔是有意義的,高選擇性就是這個字段的取值範圍很廣,好比姓名字段,會有不少的名字,可選擇性就高了。
通常來講,判斷是否須要使用索引,就能夠經過Cardinality
關鍵字來判斷,若是很是接近1,說明有必要使用,若是很是小,那麼就要考慮是否使用索引了。
須要注意的一個問題時,這個關鍵字不是及時更新的,須要更新的話,須要使用ANALYZE TABLE
,例如。
analyze table table_index;
由於目前沒有數據,因此,你會發現,這個值一直都是0,沒有變化。
在InnoDB存儲引擎中,這個關鍵字的更新發生在兩個操做中:insert和update。可是,並非每次都會更新,這樣會增長負荷,因此,對於這個關鍵字的更新有它的策略:
1/16
的數據發生變化stat_modified_conter
>2000000000默認InnoDB存儲引擎會對8個葉子節點進行採樣,採樣過程以下:
A
8
個葉子節點。統計每一個頁不一樣的記錄個數,分別爲p1-p8(p1+p2+p3+...+p8)*A/8
由於隨機採樣,因此,每次的Cardinality值都是不同的,只有一種狀況會同樣的,就是表中的葉子節點小於或者等於8,這時候,怎麼隨機採樣都是這8個,因此也就同樣的。
在MySQL 5.5以前,對於索引的添加或者刪除,每次都須要建立一張臨時表,而後導入數據到臨時表,接着刪除原表,若是一張大表進行這樣的操做,會很是的耗時,這是一個很大的缺陷。
InnoDB存儲引擎從1.0.x版本開始加入了一種Fast Index Creation(快速索引建立)的索引建立方式。
這種方式的策略爲:每次爲建立索引的表加上一個S鎖(共享鎖),在建立的時候,不須要從新建表,刪除輔助索引只須要更新內部視圖,並將輔助索引空間標記爲可用,因此,這種效率就大大提升了。
MySQL5.6開始支持的在線數據定義操做就是:容許輔助索引建立的同時,還容許其餘insert、update、delete這類DM操做,這就極大提升了數據庫的可用性。
因此,咱們可使用新的語法進行建立索引:
ALTER TABLE table_name ADD [UNIQUE|FULLLTEXT] INDEX index_name (column(length)) [ALGORITHM = {DEFAULT|INPLACE|COPY}] [LOCK = {DEFAULT|NONE|SHARED|EXLUSIVE}]
ALGORITHM
指定建立或者刪除索引的算法
old_alter_table
參數判斷,若是是OFF
,採用INPLACE
的方式LOCK表示對錶添加鎖的狀況
NONE
,如不能,判斷是否可使用SHARE
,如不能,再判斷是否可使用EXCLUSIVE
模式。聯合索引是指對錶上的多個列進行索引,這一部分咱們將經過幾個例子來說解聯合索引的相關知識點。
首先,咱們先建立一張表以及爲這張表建立聯合索引。
create table t_index( a char(2) not null default '', b char(2) not null default '', c char(2) not null default '', d char(2) not null default '' )engine myisam charset utf8;
建立聯合索引
alter table t_index add index abcd(a,b,c,d);
插入幾條測試數據
insert into t_index values('a','b','c','d'), ('a2','b2','c2','d2'), ('a3','b3','c3','d3'), ('a4','b4','c4','d4'), ('a5','b5','c5','d5'), ('a6','b6','c6','d6');
到這一步,咱們已經基本準備好了須要的數據,咱們能夠進行更深一步的聯合索引的探討。
索引創建的主要目的就是爲了提升查詢的效率,那麼聯合索引的目的也是相似的,聯合索引的目的就是爲了提升存在多個查詢條件的狀況下的效率,就如上面創建的表同樣,有多個字段,當咱們須要利用多個字段進行查詢的時候,咱們就須要利用到聯合索引了。
有時候,咱們會用聯合索引,可是,咱們並不清楚其原理,不知道何時聯合索引會起到做用,何時又是會失效的?
帶着這個問題,咱們瞭解一下聯合索引的最左匹配原則。
最左匹配原則:這個原則的意思就是建立組合索引,以最左邊的爲準,只要查詢條件中帶有最左邊的列,那麼查詢就會使用到索引。
下面,咱們用幾個例子來看看這個原則。
EXPLAIN SELECT * FROM t_index WHERE a = 'a' \G;
咱們看看這條語句的結果,首先,咱們看到使用了索引,由於查詢條件中帶有最左邊的列a,那麼利用了幾個索引呢?這個咱們須要看key_len
這個字段,咱們知道utf8編碼的一個字符3個字節,而咱們使用的數據類型是char(2)
,佔兩個字節,索引就是2*3等於6個字節,因此只有一個索引發到了做用。
EXPLAIN SELECT * FROM t_index WHERE b = 'b2' \G;
這個語句咱們能夠看出,這個沒有使用索引,由於possible_keys
爲空,並且,從查詢的行數rows
能夠看出爲6(咱們測試數據總共6條),說明進行了全盤掃描的,說明這種狀況是不符合最左匹配原則,因此不會使用索引查詢。
EXPLAIN SELECT * FROM t_index WHERE a = 'a2' AND b = 'b2' ORDER BY d \G;
這種狀況又有點不同了,咱們使用了一個排序,能夠看出使用了索引,經過key_len
爲12能夠獲得使用了2個索引a、b
,另外在Extra選項中能夠看到使用了Using filesort
,也就是文件排序,這裏使用文件排序的緣由是這樣的:上面的查詢使用了a、b索引,可是當咱們用d字段來排序時,(a,d)或者(b,d)這兩個索引是沒有排序的,聯合索引的使用有一個好處,就是索引的下一個字段是會自動排序的,在這裏的這種狀況來講,c字段就是排序的,可是d是不會,若是咱們用c來排序就會獲得不同的結果。
EXPLAIN SELECT * FROM t_index WHERE a = 'a2' AND b = 'b2' ORDER BY c \G;
是否是能夠看到,當咱們用c進行排序的時候,由於使用了a、b索引,因此c就自動排序了,因此也就不用filesort了。
講到這裏,我相信經過上面的幾個例子,對於聯合索引的相關知識已經很是的透徹清晰了,最後,咱們再來聊幾個常見的問題。
第一,建立索引和維護索引要耗費時間,這種時間隨着數據量的增長而增長。 第二,索引須要佔物理空間,除了數據表佔數據空間以外,每個索引還要佔必定的物理空間,若是要創建聚簇索引,那麼須要的空間就會更大。 第三,當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,這樣就下降了數據的維護速度。
減小開銷。建一個聯合索引(col1,col2,col3),實際至關於建了(col1),(col1,col2),(col1,col2,col3)三個索引。每多一個索引,都會增長寫操做的開銷和磁盤空間的開銷。對於大量數據的表,使用聯合索引會大大的減小開銷!
覆蓋索引。對聯合索引(col1,col2,col3),若是有以下的sql: select col1,col2,col3 from test where col1=1 and col2=2。那麼MySQL能夠直接經過遍歷索引取得數據,而無需回表,這減小了不少的隨機io操做。減小io操做,特別的隨機io實際上是dba主要的優化策略。因此,在真正的實際應用中,覆蓋索引是主要的提高性能的優化手段之一。
效率高。索引列越多,經過索引篩選出的數據越少。有1000W條數據的表,有以下sql:select from table where col1=1 and col2=2 and col3=3,假設假設每一個條件能夠篩選出10%的數據,若是隻有單值索引,那麼經過該索引能篩選出1000W10%=100w條數據,而後再回表從100w條數據中找到符合col2=2 and col3= 3的數據,而後再排序,再分頁;若是是聯合索引,經過索引篩選出1000w10% 10% *10%=1w,效率提高可想而知!
覆蓋索引 覆蓋索引是一種從輔助索引中就能夠獲得查詢的記錄,而不須要查詢彙集索引中的記錄,使用覆蓋索引的一個好處是輔助索引不包含整行記錄的全部信息,因此大小遠小於彙集索引,所以能夠大大減小IO操做。覆蓋索引的另一個好處就是對於統計問題有優化,咱們看下面的一個例子。
explain select count(*) from t_index \G;
若是是myisam引擎,Extra列會輸出Select tables optimized away
語句,myisam引擎已經保存了記錄的總數,直接返回結果,就不須要覆蓋索引優化了。
若是是InnoDB引擎,Extra列會輸出Using index
語句,說明InnoDB引擎優化器使用了覆蓋索引操做。
MySQL數據庫支持索引提示功能,索引提示功能就是咱們能夠顯示的告訴優化器使用哪一個索引,通常有下面兩種狀況可能使用到索引提示功能(INDEX HINT):
這裏咱們接着上面的例子來說解,首先,咱們先爲上面的t_index
表添加幾個索引;
alter table t_index add index a (a); alter table t_index add index b (b); alter table t_index add index c (c);
接着,咱們執行下面的語句;
EXPLAIN SELECT * FROM t_index WHERE a = 'a' AND b = 'b' AND c = 'c' \G;
你會發現這條語句就可使用三個索引,這個時候,咱們能夠顯示的使用索引提示來使用a這個索引,以下:
EXPLAIN SELECT * FROM t_index USE INDEX(a) WHERE a = 'a' AND b = 'b' AND c = 'c' \G;
這樣就顯示的使用索引a了,若是這種方式有時候優化器仍是沒有選擇你想要的索引,那麼,咱們能夠另一種方式FORCE INDEX
。
EXPLAIN SELECT * FROM t_index FORCE INDEX(a) WHERE a = 'a' AND b = 'b' AND c = 'c' \G;
這種方式則必定會選擇你想要的索引。
MySQL5.6開始支持,這種優化的目的是爲了減小磁盤的隨機訪問,而且將隨機訪問轉化爲較爲順序的數據訪問,這種優化適用於range、ref、eq_ref類型的查詢。
Multi-Range Read 優化的好處:
咱們可使用參數optimizer_switch
中的標記來控制是否開啓Multi-Range Read 優化。下面的方式將設置爲老是開啓狀態:
SET @@optimizer_switch='mrr=on,mrr_cost_based=off';
這種優化方式也是從MySQL5.6開始支持的,不支持這種方式以前,當進行索引查詢時,首先咱們先根據索引查找記錄,而後再根據where條件來過濾記錄。然而,當支持ICP優化後,MySQL數據庫會在取出索引的同時,判斷是否能夠進行where條件過濾,也就是將where過濾部分放在了存儲引擎層,大大減小了上層SQL對記錄的索取。
ICP支持range、ref、eq_ref、ref_or_null類型的查詢,當前支持MyISAM和InnoDB存儲引擎。
咱們可使用下面語句開啓ICP:
set @@optimizer_switch = "index_condition_pushdown=on"
或者關閉:
set @@optimizer_switch = "index_condition_pushdown=off"
當開啓了ICP以後,在執行計劃Extra能夠看到Using index condition
提示。
對索引中全部列都指定具體值,便是對索引中的全部列都有等值匹配的條件。
對索引的值可以進行範圍查找。
僅僅使用索引中的最左邊列進行查詢,好比在 col1 + col2 + col3 字段上的聯合索引可以被包含 col一、(col1 + col2)、(col1 + col2 + col3)的等值查詢利用到,但是不可以被 col二、(col二、col3)的等值查詢利用到。 最左匹配原則能夠算是 MySQL 中 B-Tree 索引使用的首要原則。
當查詢的列都在索引的字段中時,查詢的效率更高,因此應該儘可能避免使用 select *,須要哪些字段,就只查哪些字段。
僅僅使用索引中的第一列,而且只包含索引第一列的開頭一部分進行查找。
explain select * from t_index where a is null \G
where 'age'+10=30