INNODB存儲引擎支持如下幾種常見的索引:mysql
InnoDB存儲引擎支持的哈希索引是自適應的。會根據表的狀況自動添加算法
B+樹索引就是傳統意義上的索引,這是目前關係型數據庫系統中查找最爲經常使用和最爲有效的索引。sql
B+樹索引並不能找到一個給定鍵值的具體行。B+數索引能找到的只是被查找數據行所在的頁。而後數據庫經過把頁讀入到內存中,再在內存中查找,最後獲得要查找的數據。數據庫
有序序列使用數組
B+樹是經過二叉查找樹,再由平衡二叉樹,B樹演化而來。緩存
在二叉查找樹總左子樹的鍵值老是小於根的鍵值,右子樹的鍵值老是大於根的鍵值。因此中序遍歷能夠獲得鍵值的排序輸出。經過二叉查找樹進行查找,性能仍是能夠的。可是二叉查找樹的構造方式有不少,若是是下圖,效率就很低服務器
因此若是要最大性能構造一顆二叉查找樹,須要這顆二叉查找樹是平衡的,從而引出新定義——平衡二叉樹,又稱爲AVL樹。可是維護一顆AVL樹代價很大,每次插入都須要經過左旋右旋來保證平衡。數據結構
所以AVL多用於內存結構對象中,維護的開銷相對較小。架構
B+樹是爲磁盤或其餘直接存取輔助設備設計的一種平衡查找樹。在B+樹種,全部記錄節點都是按鍵值的大小順序排序存放在同一層的葉子節點上,又各葉子節點指針進行聯結。併發
第一種狀況:插入鍵值
第二種狀況:插入鍵值70
第三種狀況:插入鍵值95
爲了保持平衡對於新插入的鍵值,可能須要作大量的拆分頁操做,由於B+樹結構主要用於磁盤,頁的拆分意味着磁盤的操做,因此在可能的狀況下儘可能減小頁的拆分操做。因此提供了相似平衡二叉樹的旋轉功能。以下面,插入鍵值70
這樣子旋轉操做使B+樹減小了一次頁的拆分操做。
B+樹使用填充因子來控制樹的刪除變化,50%是填充因子可設的最小值。
第一種狀況:刪除鍵值70
再刪除鍵值25,仍是屬於第一種狀況
刪除鍵值60,屬於第三種狀況
數據庫中,B+樹的高度通常在2到4層,因此查詢一鍵值的行記錄最多隻須要2到4次IO。B+索引分爲彙集索引和輔助索引,區別在於葉子節點存放的是不是一整行的信息。
彙集索引就是按照每張表的主鍵構造一顆B+樹,同時葉子節點中存放的即爲整張表的行記錄數據,也將彙集索引的葉子節點成爲數據頁。彙集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構同樣,每一個數據頁都經過一個雙向鏈表來進行連接。
因爲實際的數據頁只能按照一顆B+樹進行排序,所以每張表只能擁有一個彙集索引。在多數請框下,查詢優化器傾向於採用彙集索引。由於彙集索引可以在B+樹索引的葉子節點上直接找到數據。此外,因爲定義了數據的邏輯數據,彙集索引可以特別快地訪問針對範圍值的查詢。查詢優化器可以快速發現某一段範圍的數據頁須要掃描。
數據頁上存放的是完整的每行的記錄,而在非數據頁的索引頁中,存放的僅僅是鍵值及指向數據頁的偏移量,而不是一個完整的行記錄。
彙集索引的存儲並非物理上連續的,而是邏輯上連續的。這其中有兩點,一是前面說過的頁經過雙向鏈表連接,頁按照主鍵的順序排序;另外一點是每一個頁中額記錄也是經過雙向鏈表進行維護的,物理存儲上能夠一樣不按照主鍵存儲。
彙集索引的一個好處:對於主鍵的排序查找和範圍查找速度很是快。
葉子節點並不包含行記錄的所有數據,葉子節點除了包含鍵值之外,每一個葉子節點中的索引行中還包含了一個書籤。該書籤用來告訴InnoDB存儲引擎哪裏能夠找到與索引相對應的行數據。因爲InnoDB存儲引擎表是索引組織表,所以InnoDB存儲引擎的輔助索引的書籤就是相應行數據的彙集索引鍵。
輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引。當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉級別的指針獲取指向主鍵索引的主鍵,而後再經過主 鍵索引來找到一個完整的行記錄。
索引管理
索引的建立和刪除能夠經過兩種方法:一種是ALTER TABLE,另外一種是CREATE/DROP INDEX。經過ALTER TABLE建立索引的語法是:
ALTER TABLE tb1_name | ADD {INDEX|KEY} [index_name] [index_type] (index_col_name, ...) [index_option] ... ALTER TABLE tb1_name DROP PRIMARY KEY | DROP {INDEX|KEY} index_name
經過CREATE/DROP INDEX
的語法一樣很簡單
CREATE [UNIQUE] INDEX index_name [index_type] ON tab_name (index_col_name, ...) DROP INDEX index_name ON tb1_name
建立前100個字段的索引
ALTER TABLE t ADD KEY idx_b(b(100));
建立聯合索引
ALTER TABLE t ADD KEY idx_a_c (a,c);
查看錶中索引信息
SHOW INDEX FROM t;
SHOW INDEX展示結果中每列的含義
name | value |
---|---|
table |
索引所在的表名 |
non_unique |
非惟一的索引,能夠看到primary key是0 |
key_name |
索引的名字,能夠根據這個名字來drop index |
sql_in_index |
索引中該列的位置 |
column_name |
索引列的名字 |
collation |
列以什麼方式存儲在索引中。B+樹索引老是A,使用HEAP引擎,且使用HASH索引,這裏會顯示NULL。 |
cardinality |
索引中惟一值的數目的估計值。cardinality錶行數應儘量接近1,若是很是小,用戶須要考慮是否能夠刪除此索引。 |
sub_part |
是不是列的部分索引 |
packed |
關鍵字如何被壓縮 |
null |
索引列是否含有NULL |
index_type |
索引的類型。 |
comment |
註釋 |
Cardinality值很是關鍵,優化器會根據這個值來判斷是否使用這個索引。可是這個值不是實時更新的,代價太大,因此只是一個大概值,正確是和行數一致,須要當即更新的話使用命令:
analyze table t\G;
若是Cardinality爲NULL,
在某些狀況下可能會發生索引創建了卻沒有用到的狀況。
對兩條基本同樣的語句執行EXPLAIN,可是最終出來的結果不同,一個使用索引,另外一個使用全表掃描。
解決的最好方法,就是作一次ANALYZE TABLE的操做,建議是在一個非高峯時間。
Fast Index Creation
Mysql 5.5版本以前存在的一個廣泛被人詬病的問題是MYSQL數據庫對於索引的添加或者刪除的這類操做,MYSQL數據庫的操做過程爲:
若是用戶對於一張大表進行索引的添加和刪除操做,會須要很長的時間,更關鍵的是,如有大量事務須要訪問正在被修改的表,這意味着數據庫服務不可用。
從InnoDB 1.0.X版本開始支持一種稱爲fast index creation
快速索引建立的索引建立方式——簡稱FIC。
對於輔助索引的建立,會對建立索引的表加上一個S鎖。在建立的過程過程當中,不須要重建表,所以速度較以前提升不少,而且數據庫的可用性也獲得了提升。刪除輔助索引操做就更簡單了,InnoDB存儲引擎只需更新內部視圖,並將輔助索引的空間標記爲可用,同時刪除MYSQL數據庫內部視圖上對該表的索引定義便可。
這裏須要注意的是:臨時表的建立路徑是經過參數tmpdir進行設置的,用戶必須保證tmpdir有足夠的空間能夠存放臨時表,不然會致使建立索引失敗。因爲FIC在索引建立的過程當中對錶機上了S鎖,所以在建立的過程當中只能對該表進行讀操做,如有大量的事務須要對目標庫進行寫操做,那麼數據庫的服務一樣不可用。
FIC方式只限定於輔助索引。
Online Schema Change
——在線架構改變
所謂「在線」是指事務的建立過程當中,能夠有讀寫事務對錶進行操做,提升了原有MYSQL數據庫在DDL操做時的併發性。是經過PHP腳本開發的。
實現OSC步驟以下:
init
:初始化階段,對建立的表作一些驗證工做,如檢查表是否有主鍵,是否存在觸發器或者外鍵等。createCopyTable
:建立和原始表結構同樣的新表alterCopyTable
:對建立的新表進行ALTER TABLE操做,如添加索引或列等。createDeltasTable
:建立deltas
表,該表的做用是爲下一步建立的觸發器所使用。以後對元彪的全部DML操做會被記錄到createDeltasTable
中。createTriggers
:對原表建立INSERT、UPDATE、DELETE操做的觸發器。觸發操做產生的記錄被記錄到的deltas
表中。startSnpshotXact
:開始OSC操做的事務。selectTableIntoOutfile
:將原表的數據寫入到新表。爲了減小對原表的鎖定時間,這裏經過分片將數據輸出到多個外部文件,而後將外部文件的數據導入到copy表中。分片的大小能夠指定。dropNCIndexs
:在導入到新表前,刪除新表中全部的輔助索引。loadCopyTable
:將導出的分片文件導入到新表。replayChanges
:將OSC過程當中原表DML操做的記錄應用到新表中,這些記錄被保存在deltas
表中。recreateNCIndexes
:從新建立輔助索引。replayChanges
:再次進行DML日誌的回放操做,這些日誌是在上述建立輔助索引中過程新產生的日誌。swapTables
:將原表和新表交換名字,整個操做須要鎖定2張表,不容許新的數據產生。因爲更名是一個很快的操做,所以堵塞的時間很是短。有必定侷限性,要求進行修改的表必定要有主鍵,且表自己不能存在外鍵和觸發器。此外,在進行OSC過程當中,容許sql_bin_log=0
,所以所作的操做不會同步到slave服務器,可能致使主從不一致的狀況。
Online DDL
——在線數據定義
在5.6版本開始支持,容許輔助索引建立的同時,進行INSERT、UPDATE、DELETE等DML操做。
如下操做均可以經過Online DDL
進行操做
經過新的ALTER TABLE
語法,用戶能夠選擇索引的建立方式:
ALTER TABLE tb1_name | ADD {INDEX | KEY} [index_name] [index_type] (index_col_name, ...) [index_option] ... ALGORITHM [=] {DEFAULT | INPLACE | COPY} LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE}
ALGORITHM
指定了建立或刪除索引的算法:
COPY
:按照5.1版本以前的工做模式,即建立臨時表的方式。INPLACE
:表示索引建立或刪除不須要建立臨時表。DEFAULT
:表示根據參數old_alter_table
來判斷是經過INPLACE
仍是COPY
算法。該參數默認爲OFF。表示採用INPLACE
的方式。SHOW VARIABLES LIKE 'old_alter_table'\G;`
LOCK
部分爲索引建立或刪除時對錶添加鎖的狀況,可有的選擇爲:
NONE
:執行索引建立或刪除操做時,對目標表不添加任何的鎖,即事務仍然能夠進行讀寫操做,不會受到阻塞。所以這種模式能夠得到最大的併發度。SHARE
:和以前的FIC相似,執行索引建立或刪除操做時,對目標庫加上一個S鎖,對於併發的讀事務,依然能夠執行,可是遇到寫事務,就會發生等待操做。若是存儲引擎不支持SHARE模式,會返回一個錯誤信息。EXCLUSIVE
:執行索引建立或刪除操做時,對目標庫加上一個X鎖。讀寫事務都不能進行,所以會堵塞全部的線程,這和COPY方式運行獲得的狀態相似,可是不須要像COPY方式那樣建立一張臨時表。DEFAULT
:首先會判斷當前操做是否可使用NONE模式,若不能,則判斷是否可使用SHARE模式,最後判斷是否可使用EXCLUSIVE模式。InnoDB
存儲引擎實現Online DDL
的原理是在執行建立或者刪除操做的同時,將INSERT\UPDATE\DELETE
這類操做寫入到一個緩存中,待完成索引建立後再將重作應用在表上,一次打到數據的一致性。這個緩存的大小由參數innodb_online_alter_log_max_size
控制,默認的大小128MB。
須要特別注意的時候,在索引建立過程當中,SQL優化器不會選擇正在建立中的索引。
Cardinality
值Cardinality
通常經驗是:在訪問表中不多一部分數據時使用B+樹索引纔有意義。
對於低選擇字段(性別、地區、類型)沒有必要添加B+樹索引。對於高選擇性字段(幾乎不重複)添加索引最爲合適。
如何查看索引是不是高選擇性?經過SHOW INDEX
結果的列Cardinlity
來觀察。表示索引中不重複記錄數量的預估值。實際應用中Cardinlity/n_rows_in_table
應該約等於1,若是很是小,那麼須要考慮是否須要建立這個索引。
InnoDB
存儲引擎的Cardinality
統計數據庫對於Cardinality
的統計是經過採樣的方法來完成的。
Cardinality
統計信息的更新發生在兩個操做中:INSERT和UPDATE
。
更新策略爲:
stat_modified_counter>2000000000
,發生變化的次數更新Cardinality
的方法:
Cardinality
的預估值。`Cardinality=(P1+P2+……+P8)*A/8。相關參數
innodb_stats_sample_pages
:設置採樣數,默認爲8innodb_stats_method
:設置對待NULL值的策略
null_equal
:默認值,全部空值視爲一種。null_unequal
:空值視爲不一樣種狀況nulls_ignored
:忽略空值狀況當執行SQL語句ANALYZE TABLE
、SHOW TABLE STATUS
、SHOW INDEX
以及訪問INFORMATION_SCHEMA
架構下的表TABLE
和STATISTICS
會致使InnoDB
存儲引擎去從新計算索引的Cardinality
值。若表數量很大,或者存在多個輔助索引的時候,執行上述操做會很慢,因此提供了相關參數來設置,不更新Cardinality
值。
參數 | 說明 |
---|---|
innodb_stats_persistent |
是否將命令ANALYZE TABLE 計算獲得的Cardinality 值存放到磁盤上。默認爲OFF |
innodb_stats_on_metadata |
當執行SQL語句SHOW TABLE STATUS 、SHOW INDEX 以及訪問INFORMATION_SCHEMA 架構下的表TABLE 和STATISTICS是否 從新計算索引的Cardinality 值。默認OFF |
innodb_stats_persistent_sample_pages |
若innodb_stats_persistene 設置爲ON,表示採樣值,默認值20 |
innodb_stats_transient_sample_pages |
替代innodb_stats_sample_pages ,表示每次採樣頁數量,默認值8 |
CREATE TABLE t ( a INT, b INT, PRIMARY KEY (a), KEY idx_a_b (a,b) )ENGINE=INNODB
# 對於a b列查詢可使用聯合索引(a,b) select * from table where a = xxx and b = xxx; # 對於單個a列的查詢可使用聯合索引(a,b) select * from table where a = xxx; # 對於單個b列的查詢則不可使用聯合索引(a,b),由於葉子節點上的b值爲1,2,1,4,1,2,顯然不是排序的 select * from table where b = xxx;
使用聯合索引的話,已經對第二個鍵值進行了排序操做。例如,不少狀況下應用程序都要查找某個用戶的購物狀況,並按照時間進行排序,而後取出最近三次的購買記錄,這時候使用聯合索引能夠避免多一次的排序操做。
一個Demo.
CREATE TABLE buy_log ( userid INT UNSIGNED NOT NULL, buy_date DATE )ENGINE=InnoDB; INSERT INTO buy_log VALUES (1, '2009-01-01'); INSERT INTO buy_log VALUES (2, '2009-01-01'); INSERT INTO buy_log VALUES (3, '2009-01-01'); INSERT INTO buy_log VALUES (1, '2009-02-01'); INSERT INTO buy_log VALUES (3, '2009-02-01'); INSERT INTO buy_log VALUES (1, '2009-03-01'); INSERT INTO buy_log VALUES (1, '2009-04-01'); ALTER TABLE buy_log ADD KEY (userid) ALTER TABLE but_log ADD KEY (userid, buy_date); # ------------------------------------- # 查詢一個數據,使用索引KEY(userid) SELECT * FROM buy_log WHRER userid=2; # 查詢最近3次購買記錄的數據,使用索引KEY(userid, buy_date),並且無需再對buy_date作一次額外的排序操做。 SELECT * FROM buy_log WHRER userid=2;
定義:從輔助索引中就能夠獲得查詢的記錄,而不須要查詢彙集索引中的記錄。
好處:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做。
查詢輔助索引對應的字段,能夠直接經過輔助索引查詢獲得,不須要查詢彙集索引。
#對於InnodbDB存儲引擎的輔助索引而言,因爲包含了主鍵信息,所以其葉子節點存放的數據爲(primary key1,primary key2,... , key1, key2,...),則下列語句可使用一次輔助聯合索引來完成。 SELECT KEY2 FROM table WHERE KEY1=xxx; SELECT primary key2, KEY2 FROM table WHERE KEY1=xxx; SELECT primary key1, KEY2 FROM table WHERE KEY1=xxx; SELECT primary key1, primary key2, KEY2 FROM table WHERE KEY1=xxx;
對於統計問題而言,能夠減小IO操做
SELECT COUNT(*) FROM buy_log;
在某些狀況下,當執行EXPLAIN
命令進行SQL語句的分析時,會發現優化器並無選擇索引去查找數據,而是經過掃描彙集索引,也就是直接進行全表的掃描來獲得數據。這種狀況多發生於範圍查找,JOIN鏈接等狀況下。
一個demo
select * from orderdetails where orderid > 10000 and orderid < 102000;
這個表上的索引有:
可是經過EXPLAIN
命令,能夠發現優化器並無按照OrderID
上的索引來查找數據。
最後使用了主鍵,進行權標掃描。主要緣由是,用戶選擇的數據是整個行信息,而OrderID
索引不能覆蓋到咱們要查詢的信息,所以在對OrderID
索引查到指定數據後,還須要一次書籤訪問來查找整行數據的信息。雖然OrderID索引中數據是順序存放的。可是再一次進行書籤查找的數據則是無序的,所以變爲了磁盤上的離散讀操做。若是數據量小,還會使用輔助索引,可是數據量大的時候,依舊選擇使用匯集索引來查找數據。
若是用戶使用SSD盤,能夠是用FORCE INDEX
來強制使用某個索引。
MYSQL數據庫支持索引提示INDEX HINT
,顯式地告訴優化器使用哪一個索引。使用的兩種狀況:
# 使用USE INDEX,可是不一必定生效,只是告訴優化器,可使用這個索引 select * from t use index(a) where a = 1 and b = 2; # 使用FROCE INDEX,能夠強制使用某個索引執行 select * from t force index(a) where a = 1 and b = 2;
Multi-Range Read
優化 目的就是爲了減小磁盤的隨機訪問,而且將隨機訪問轉化爲順序的數據訪問,這對於IO-bound類型的SQL查詢語句可帶來性能極大的提高。Multi-range Read
優化可適用於range,ref,eq_ref
類型的查詢。
MRR優化的好處:
對於InnoDB
和MyISAM
存儲引擎的範圍查找和JOIN查詢工做,MRR的工做方式以下:
場景一:
若Innodb
或者MyISAM
存儲引擎的緩衝池不足夠大,不能存放下一張表的全部數據,此時頻繁的離散讀操做會致使緩存中的頁被替換出緩衝池,又不斷地讀入緩衝池。如果按照主鍵順序進行訪問,則能夠將此重複息行爲降到最低。
select * from salaries where salary>10000 AND salary<40000;
場景二:
能夠將某些範圍查詢,拆分爲鍵值對,以此進行批量的數據查詢。
select * from t where key_part1 >= 1000 and key_part1 < 2000 and key_part2 = 10000;
這個表上有(key_part1, key_part2)的輔助聯合索引,在不採用MRR的時候,會先按照key_part1
在1000和2000的數據所有查出來,而後再按照key_part2
進行過濾。因此這個時候啓用MRR,他會先把過濾條件拆解爲(1000,1000),(1001,1000)……而後再進行查詢。
參數設置:
optimizer_switch
控制是否啓用MRR優化。
# 老是啓用MRR優化 set @@optimizer_switch = 'mrr=on, mrr_cost_based=off';
read_rnd_buffer_size
控制鍵值的緩衝區大小。默認值256KB。當大於改值時,執行器對已經緩存的數據根據ROWID進行排序,並經過ROWID來取得行數據。
select @@read_rnd_buffer_size\G;
Index Condition Pushdown
ICP優化 在支持ICP優化後,MYSQL數據庫會在取出索引的同時,判斷是否能夠進行where條件的過濾,也就是將WHERE的部分過濾操做放在了存儲引擎層。
支持range
、ref
、eq_ref
、ref_or_null
類型的查詢。當優化器選擇ICP優化時,extra看到using index condition
提示。
哈希表也稱散列表,由直接尋址表改進而來。
直接尋址表就是用一個數組來來記錄每一個數據存放的位置。
可是若是數據很大,那麼這個數組就要很大。相反,若是開闢了很大的數組,可是沒什麼數據使用,就浪費了空間,因此提出了哈希表。
在哈希的方式下,改元素處於h(k)中,利用哈希函數h,根據關鍵字k計算出槽的位置,函數h將關鍵字域U映射到哈希表T[0,...,m-1]的槽位上。
可是可能連個關鍵字映射到同一個槽上,這種狀況稱爲碰撞,數據庫中結局碰撞的最簡單方法是使用鏈表法。
通常哈希函數須要可以很好的散列,最簡單的除法散列法:h(k) = k mod m
INNODB存儲引擎採用哈希算法對字典進行查找,其衝突機制採用鏈表方式,哈希函數採用除法散列方式。
對於緩衝池頁的哈希表來講,在緩衝池中的Page頁都有一個chain指針,它指向相同哈希函數值的頁。而對於除法散列,m的取值爲略大於2倍的緩衝池頁數量的質數。
查找方式是:表空間都有一個space_id,用戶查詢的應該是某個表空間的某個連續16KB的頁,即偏移量Offset。關鍵字計算公式爲:K = space_id << 20 + space_id + offset
,而後經過除法散列到各個槽中。
數據庫自身建立並使用,不能進行干預。自適應哈希索引景哈希函數映射到一個哈希表中,所以對字典類型的查找很是迅速。可是對範圍查找就無能爲力了。
select * from table where index_col = 'xxx'