MySQL 索引
索引介紹
在SQL/">MySQL中, 索引是高效獲取資料的最重要的資料結構
,通常在表資料越來越多情況下獲取資料的效率開始下降,而索引或者叫做鍵可以有效提升效率。
理解索引工作的方式最好的辦法就是把 索引比喻成書的目錄
,當需要檢視特定的章節時通過檢視目錄的方式往往要比檢視整個書的內容要有效很多。
當 索引包含多個欄位
時, 索引欄位的順序就非常重要
,因為MySQL是從左開始匹配使用索引,意味著如果沒有最左邊欄位時,語句是用不了索引。
使用索引的優勢在於: 大大減少伺服器需要掃描的資料量 幫助伺服器避免排序和臨時表 可以將隨機IO變成順序IO
索引在帶來上述優勢的同時,也有缺點在;
在建立索引和維護索引時會耗費時間,而且隨著資料量的增加而增加
索引檔案會佔用物理空間
當對錶的資料進行Insert,update,delete操作時,索引也要動態維護,這就降低了DML的執行效率
B-Tree索引
B-tree索引 顧名思義, B-tree索引使用B-tree的資料結構儲存資料
,不同的儲存引擎以不同的方式使用B-Tree索引,比如MyISAM使用字首壓縮技術使得索引空間更小,而InnoDB則按照原資料格式儲存,且 MyISAM索引在索引中記錄了對應資料的物理位置,而InnoDB則在索引中記錄了對應的主鍵數值
。
下圖展示InnoDB的B-tree索引結構
B-Tree索引 驅使儲存引擎不再通過全表掃描獲取資料,而是 從索引的根節點開始查詢
,在根節點和中間節點都存放了指向下層節點的指標, 通過比較節點頁的值和要查詢值可以找到合適的指標進入下層子節點,直到最下層的葉子節點
, 最終的結果就是要麼找到對應的值,要麼找不到對應的值。整個B-tree樹的深度和表的大小直接相關。
B-Tree對索引列是順序組織儲存的,所以也很適合查詢範圍資料。
如下圖
使用B-Tree索引適用於全鍵值、鍵值範圍或者鍵字首查詢
- 全鍵值匹配:和索引中的所有列都進行匹配,比如查詢姓名為zhang san,出生於1982-1-1的人
- 匹配最左字首:和索引中的最左邊的列進行匹配,比如查詢所有姓為zhang的人
- 匹配列字首:匹配索引最左邊列的開頭部分,比如查詢所有以z開頭的姓名的人
- 匹配範圍值:匹配索引列的範圍區域值,比如查詢姓在li和wang之間的人
- 精確匹配左邊列並範圍匹配右邊的列:比如查詢所有姓為Zhang,且名字以K開頭的人
- 只訪問索引的查詢:查詢結果完全可以通過索引獲得,也叫做覆蓋索引,比如查詢所有姓為zhang的人的姓名
建立測試資料
CREATE TABLE people ( last_name varchar(50) not null, first_name varchar(50) not null, dob date not null, gender enum('m', 'f') not null, key(last_name, first_name, dob) ); insert into people values('zhang','san','1982-1-1','m'); insert into people values('li','si','1985-3-2','m'); insert into people values('wang','wu','1988-6-15','f');
B-Tree索引也有一定的限制:
如果查詢條件不是按照索引最左列開始查詢,則無法使用索引,比如如果查詢名字為san的人、查詢某個特定生日的人、檢視姓氏以某字母結尾的人都無法使用此索引
如果查詢條件跳過了索引的中間列,則查詢使用索引僅使用最左邊的列,比如查詢姓為zhang且在某個特定日期出生的人
如果查詢的某列有範圍查詢,則其右邊的列無法使用索引優化查詢,如查詢姓為zhang,名字以s開頭且生日為1982-1-1的人,則查詢只能使用前兩列。
雜湊索引
MySQL中只有Memory引擎支援雜湊索引,但Memory引擎也支援B-Tree索引。雜湊索引是基於雜湊表實現, 只有精確匹配索引中的所有列的查詢才有效。對每一行資料,會將所對應的索引列計算出一個雜湊碼, 在索引中存放此雜湊碼和對應資料行的指標。當存在多行資料的雜湊碼相同時,索引會以連結串列的方式存放多個記錄指標到同一個雜湊條目中。也有不同的索引列值卻有相同雜湊碼的情況,叫雜湊衝突,當存在雜湊衝突時, 儲存引擎必須遍歷連結串列中所有行指標,於對應的行資料比較,直到找到所有符合條件的行。
建立一些測試表和資料
CREATE TABLE testhash ( fname VARCHAR(50) NOT NULL, lname VARCHAR(50) NOT NULL, KEY USING HASH(fname) ) ENGINE=MEMORY; insert intotesthash values ("Arjen", "Lentz"), ("Baron","Schwartz"), ("Peter","Zaitsev"), ("Vadim","Tkachenko"); mysql> SELECT * FROM testhash; +--------+-----------+ | fname | lname | +--------+-----------+ | Arjen | Lentz | | Baron | Schwartz | | Peter | Zaitsev | | Vadim | Tkachenko | +--------+-----------+
雖然 InnoDB表建立索引時指定hash索引也能建立成功,但底層建立的索引依舊是btree索引
create table TIhash(fname varchar(10),lname varchar(10),key using hash(fname));
看看錶結構
看看底層用的索引
雜湊索引的限制:
雜湊索引只支援等值比較查詢,比如=,IN操作等,不支援範圍查詢
如下
explain select * from TIhash where fname=’a’;
explain select * from TIhash where fname>’a’;
explain select * from TIhash where fname in (‘a’,’b’);
全文索引
Fulltext索引是建立在 char, varchar, text文字欄位
上的加速查詢和DML操作的索引。 它不是直接比較索引中的值,而是查詢文字中的關鍵詞,所以全文索引更類似於搜尋引擎而不是簡單的where條件匹配
用match()…against語句
建立表的適合新增全文索引
CREATE TABLE `article` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), FULLTEXT (content) );
修改表結構新增全文索引 ALTER TABLE article ADD FULLTEXT index_content(content)
直接建立索引
CREATE FULLTEXT INDEX index_content ON article(content)
全文搜尋的語法: MATCH(col1,col2,…) AGAINST (expr[search_modifier])。其中
MATCH中的內容為已建立FULLTEXT索引並要從中查詢資料的列, AGAINST中的expr為要查詢的文字內容, search_modifier為可選搜尋型別。 search_modifier的可能取值有:
IN NATURAL LANGUAGEMODE、 IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION、 IN BOOLEAN MODE、 WITH QUERY EXPANSION。 search_modifier 的每個取值代表一種型別的全文搜尋,分別為自然語言全文搜尋、帶查詢擴充套件的自然語 言全文搜尋、布林全文搜尋、查詢擴充套件全文搜尋(預設使用IN NATURAL LANGUAGE MODE) 。
插入測試資料
Insert into article values(1,'ac','abcd',1), (2,'bd','bcde',1), (3,'ab','defg',1), (4,'be','degh',1);
使用全文索引方法如下
SELECT * FROM tablename WHERE MATCH(column1, column2) AGAINST(‘xxx’, ‘sss’, ‘ddd’)
索引建立
mysql> help create index Name: 'CREATE INDEX' Description: Syntax: CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [index_type] ON tbl_name (index_col_name,...) [index_option] [algorithm_option | lock_option] ... index_col_name: col_name [(length)] [ASC | DESC] index_option: KEY_BLOCK_SIZE [=] value | index_type | WITH PARSER parser_name | COMMENT 'string' index_type: USING {BTREE | HASH} algorithm_option: ALGORITHM [=] {DEFAULT|INPLACE|COPY} lock_option: LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE}
Index_col_name 可以包含一個欄位,也可以包含多個欄位
(逗號隔開),如果包含多個欄位,則表明此索引是複合索引
Unique index代表索引中的值不能有重複
Fulltext index只能建立在innodb和myisam儲存引擎的char,varchar和text欄位上
Index可以建立在包含NULL值的欄位上
Key_block_size=value是在myisam儲存引擎的表上指定索引鍵的block大小
建立索引的是三種途徑包括:
-
直接建立索引 CREATE INDEX index_name ON table(column(length))
-
修改表結構的方式新增索引 ALTER TABLE table_name ADD INDEX index_name ON (column(length))
-
建立表的時候同時建立索引
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), INDEX index_name (title(length)) );
刪除索引
DROP INDEX index_name ON table
例如:
create index idx_st_sname on students(sname); ##建立普通索引 create index idx_st_union on students(sname,sex); ##建立複合索引 create unique index idx_st_sid on students(sid); ##建立唯一索引
mysql 儲存引擎支援的索引型別
儲存引擎 | 索引型別 |
InnoDB | BTREE |
MyISAM | BTREE |
MEMORY | HASH,BTREE |
NDB | HASH,BTREE |
Create index註釋
Comment ‘string’ 代表可以為索引新增最長1024字元的註釋
CREATE TABLE t1 (id INT); CREATE INDEX id_index ON t1 (id) COMMENT 'MERGE_THRESHOLD=40';
建立唯一索引
與普通索引類似,不同的就是在索引功能的基礎上又增加了對資料的唯一性要求,但和主鍵索引不同的是允許空值,如果是多列索引,則列的組合在每行都必須唯一,否則資料插入失敗。其建立的方法和普通索引類似:
建立唯一索引 CREATE UNIQUE INDEX indexName ON table(column(length)) length 字元數
修改表結構 ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
建立表的時候直接指定索引
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), UNIQUE indexName (title(length)) );
索引的刪除
DROP INDEX `index_name` ON `table_name` ALTER TABLE `table_name` DROP INDEX `index_name`
這兩句都是等價的,都是刪除掉table_name中的索引index_name;
ALTER TABLE table_name
DROP PRIMARY KEY
– 刪除主鍵索引,注意主鍵索引只能用這種方式刪除
MySQL索引檢視
通過執行show create table table_name檢視基本索引資訊
mysql> show create table students; | students | CREATE TABLE `students` ( `sid` int(11) NOT NULL, `sname` varchar(10) DEFAULT NULL, `gender` int(11) DEFAULT NULL, `dept_id` int(11) DEFAULT NULL, PRIMARY KEY (`sid`), KEY `idx_s_1` (`dept_id`), KEY `idx_union_1` (`gender`,`dept_id`) ) ENGINE=InnoDB DEFAULTCHARSET=utf8 COLLATE=utf8_unicode_ci
通過執行show index from table_name\G;命令檢視索引的資訊
1.Table 表的名稱。 2.Non_unique 如果索引不能包括重複詞,則為0。如果可以,則為1。 3.Key_name 索引的名稱。 4.Seq_in_index 索引中的列序列號,從1開始。
例如聯合索引 左邊第一個序列號是1 , 第二個是2
create index idx_union on course (course_name,teacher_id);
5.Column_name 列名稱。
6.Collation
列以什麼方式儲存在索引中。在MySQL中,有值‘A’ (升序)或NULL(無分類)。
7.Cardinality
索引中唯一值的數目的估計值
。通過執行ANALYZE TABLE table_name或myisamchk (shell命令)可以更新。基數根據被儲存為整數的統計資料來計數,所以即使對於小型表,該值也沒有必要是精確的。基數越大,當進行聯合時,mysql使用該索引的機會就越大
8.Sub_part
如果列只是被部分地編入索引,則為被編入索引的字元的數目。如果整列被編入索引,則為NULL。
9.Packed 指示關鍵字如何被壓縮。如果沒有被壓縮,則為NULL。
10.Null
如果列含有NULL,則含有YES。如果沒有,則該列含有NO。
11.Index_type 索引型別(BTREE, FULLTEXT, HASH, RTREE)。
12.Comment
索引註釋
MySQL聚簇索引和輔助索引
每個InnoDB表都會有一個特殊的索引,叫聚簇索引,索引中包含了所有的行資料。 聚簇索引和主鍵是一個意思的兩種叫法。
當顯示定義一個主鍵時,則InnoDB就把它作為聚簇索引,當表中沒有代表唯一的一個或一組欄位時,可以增加一個auto-increment欄位作為主鍵
當沒有定義主鍵時,則MySQL會尋找是否有非NULL的唯一索引,如果有就把第一個唯一索引作為聚簇索引 當沒有定義主鍵時,則MySQL會尋找是否有非NULL的唯一索引,如果有就把第一個唯一索引作為聚簇索引
當沒有主鍵或合適的唯一索引時,InnoDB內部會建立一個虛構的聚簇索引,其中包含row ID。
聚簇索引的優勢:
當SQL語句通過聚簇索引訪問表資料時,由於通過索引能直接定位並訪問表資料,所以效能很高。
相關資料會儲存在一起,比如表是包含使用者的郵件資訊,通過使用者ID建立聚簇索引,則查詢一個使用者的所有郵件只需要讀取少量的資料頁。
使用覆蓋索引掃描的查詢可以直接使用頁節點上的主鍵值
聚簇索引的葉子節點包含了行的全部資料,而節點頁只包含了索引列,比如下圖索引列
所有非聚簇索引都叫做輔助索引, 在InnoDB裡,輔助索引的每一行包含了對應的主鍵值和輔助索引值,索引對輔助索引的SQL執行是先定位對應的主鍵值,然後再到聚簇索引中查詢對應的行資料
。
針對同一個表分別使用MyISAM和InnoDB儲存引擎建立主鍵索引和普通索引,看看索引的資料結構不同
CREATE TABLE layout_test ( col1 int NOT NULL, col2 int NOT NULL, PRIMARY KEY(col1), KEY(col2) );
針對上表,主鍵上隨機插入1~10000的隨機整數,col2上隨機插入1到100的隨機整數。
MyISAM索引資料分佈
MyISAM儲存引擎對定長的行資料採用行號, 行號從0開始遞增, 按照資料插入的順序儲存在磁碟上
主鍵資料分佈情況如下:
輔助索引的資料分佈和主鍵一致,情況如下:

每個葉子節點的對應的是行號和對應的鍵值 所以MyISAM中的主鍵索引和其他索引在結構上沒有什麼不同。 例行號0 ,值是99,資料按照順序插入在磁碟上。
InnoDB索引資料分佈
因為InnoDB支援聚簇索引,所以使用了不同的方式儲存同樣的資料。 在InnoDB中,聚簇索引相當於表,不像MyISAM一樣需要獨立的行儲存。
聚簇索引資料分佈情況如下:
聚簇索引的 每個葉子節點包含了主鍵值,事務ID,用於事務和多版本併發控制的回滾指標以及其他剩餘的列。
另外, InnoDB的二級索引也和MyISAM不同,其葉子節點中儲存的不是行指標,而是主鍵值
,從而當出現行移動或者資料頁分裂時無需更新二級索引中的該值,
而當出現行移動或者資料頁分裂時無需更新二級索引中的該值,
MyISAM和InnoDB的索引資料對比情況如下:
聚簇 :二級(輔助) 索引鍵值對應主鍵的鍵值 , 非聚簇 : 鍵值對應的行號, 指向關係
InnoDB索引物理結構
InnoDB索引的物理結構是B-tree結構,所有的索引資料都存放在B-tree的葉節點上。預設的索引頁的大小為16KB。 當新資料插入到聚簇索引時,InnoDB會保留1/16的索引頁空閒空間作為將來的資料插入和修改所用。
在建立和重建和B-tree索引時,InnoDB會執行bulk載入將資料載入到索引頁中。當索引頁的資料佔據的空間低於設定的merge_threshod,預設是50%時,則InnoDB會執行索引頁的合併並將當前索引頁釋放。
在資料庫例項之間複製資料檔案和日誌檔案要確保使用相同的頁大小,否則無法使用。
索引使用策略
如何建立高效的索引
-
索引建立在經常搜尋的列上
在慢查詢日誌看看經常使用一些查詢語句,在經常使用的列上建立索引,才能保證索引會被重複使用,從而發揮索引提升語句執行效能的優勢,反之如果建立在很少使用的列上則索引帶來的劣勢要大於優勢 對於經常使用的列常見於where條件中,表連線中的關聯欄位,以及排序欄位等。 - 使用獨立的列
獨立的列是指索引列不能是表示式的一部分,也不能是函式的一部分
比如以下的兩個例子就用不了索引
準備測試資料:
create table students_1 ( sid int(11)auto_increment not null, sname varchar(64) default null, gender int(11) default null, dept_id int(11) default null, birthday datetime default null, primary key (sid), key inx_name (sname) ) engine=innodb; insert into students_1 values(1,'a',1,1,now()),(2,'b',1,1,now());
正常走主鍵索引
等號左邊是一個表示式 查到的結果是跟sid=1 一樣,但是不會走索引,表示式最好放等號右邊才會走索引
避免使用函式
在索引欄位上儘量避免使用函式,否則該索引不會被使用。
- 字首索引 當
要增加索引的欄位是很長的字元列時
,索引會變得很大和很慢,所以要考慮在列開始的部分字串上建立索引
,這裡要求索引的選擇性,即不重複的索引值和表的記錄總數對比的比例約接近1越好
,因為選擇性越高意味著在查詢時能篩選掉的資料量就越多。
但字首索引的缺點是無法用在order by和group by情況下,且也無法執行覆蓋掃描。
評估字首索引是否足夠好,可以通過以下方法對比:
找區別度 , 插入資料建立字首索引測試:
DROP INDEX inx_nameon students_1; delete from students_1; insert into students_1 values (1,'aaaabbcbdaaavvsdfasdfgasdfasdfasdfasdfasdfgasdfgadsf',1,1,now()), (2,'aaacbbcbdaaavvsdfasdfgasadfasdfasdfsadfsdfgasdfgadsf',1,1,now()), (3,'aaaebbcbdaaavvsdfasdfasdfaasdfasdfsdfgasdfgasdfgadsf',1,1,now()), (4,'aaagbbcbdaaavvsdfasdfgasdasdfasdfasdfasdffgasdfgadsf',1,1,now());
例如 : left(sanme,3) # 是看前3個字元的區別度
SELECT COUNT(DISTINCT LEFT(sname, 1))/COUNT(*) AS sel3, COUNT(DISTINCT LEFT(sname, 2))/COUNT(*) AS sel4, COUNT(DISTINCT LEFT(sname, 3))/COUNT(*) AS sel5, COUNT(DISTINCT LEFT(sname, 4))/COUNT(*) AS sel6, COUNT(DISTINCT LEFT(sname, 5))/COUNT(*) AS sel7 FROM students_1;
所以在字元長度為4 的建立字首索引即可
mysql> create index inx_s_4 on students_1(sname(4));
會走索引
字首索引無法用在分組的情況下
- 多列索引 通常一般的索引是建立在單獨的一個列上,但索引也可以建立在多個列上,這就要求對列的前後順序的選擇。
索引的錯誤使用方法是在所有限制條件語句涉及的欄位上都建立單獨的索引
,而是應該判斷多個列的組合是否經常出現而且組合之後的資料篩選範圍要優於單獨的索引使用。
如果答案是肯定的,那就可以在多個列上建立多列索引。比如:
create index ind_union on students(sname, dept_id, teacher_id);
- 覆蓋索引 當出現 語句的執行過程所
需要的資料完全可以通過索引來獲得時,這樣的索引叫做覆蓋引
,常常出現在多列索引的情況
, 對於經常出現的SQL語句,可以通過建立覆蓋索引而使得語句執行不需要掃描表資料,從而加快語句的執行效率。 當發起一個覆蓋索引的查詢時,在explain語句的Extra欄位中可以看到“Using index” 的資訊,代表發生了在索引身上的覆蓋查詢。
mysql> create index idx_union_1 on students_1 (gender,dept_id);
覆蓋索引是非常有用的工具,能夠極大的提高效能,其主要優勢在於:
a. 索引大小通常遠小於資料行大小,所以如果只需要讀取索引,那MySQL會極大地減少資料訪問量 b. 因為索引是按照列值順序儲存的,所以對IO密集型的範圍查詢會比隨機從磁碟讀取每一行資料的IO要少很多。 c. InnoDB的二級索引能夠覆蓋查詢的話,就可以避免對主鍵索引的二次查詢
由於覆蓋索引必須要儲存索引列的值,而雜湊索引、空間索引和全文索引等不儲存索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引。
索引列順序選擇
當建立的索引包含多個欄位時,欄位在索引中的順序就非常重要
, 正確的順序依賴於使用索引的查詢語句。此要求僅限於B-Tree索引,其他型別的索引則不關注索引列順序。
通常的做法是基於全域性基數和選擇性來決定欄位順序, 某個列的選擇性高就意味著更能首先過濾掉大部分無用的資料,所以就將此列作為索引的第一列
Customer_id 列的選擇性(過濾條件)更高,所以應該將其作為索引列的第一列。
索引統計資訊
MySQL查詢優化器通過兩個API來了解儲存引擎的索引值的分佈資訊,以決定如何使用索引。 如果儲存引擎向優化器提供的掃描行數資訊不準確,或執行計劃太複雜以致於無法準確獲取匹配的行數,那優化器會使用索引統計資訊來估算掃描行數。 如果表沒有統計資訊,或者統計資訊不準確,優化器很可能做出錯誤的決定。可以通過analyze table命令來重新生成統計資訊.
可以通過執 行show index from命令來檢視索引的基數cardinality(儲存引擎估算索引列 有多少不同的值
)等資訊 :
cardinality 越高 : 走索引的概率也就越高,基數越低走索引的越低
還可以通過檢視information_schema.statistics表來檢視索引的資訊。
InnoDB儲存引擎通過抽樣的方式計算統計資訊,首先隨機讀取少量的索引頁面,以此為樣本計算索引的統計資訊,通過引數innodb_stats_sample_pages引數來設定樣本頁的數量。
mysql> show variables like '%innodb_stats_sample_pages%'; +---------------------------+-------+ | Variable_name| Value | +---------------------------+-------+ | innodb_stats_sample_pages | 8| +---------------------------+-------+
InnoDB會在 表首次開啟,或者執行analyze table
,或者 表的大小變化超過1/16
時,都 會計算索引的統計資訊
。 如果希望持久化索引的統計資訊,則可以在5.6或更高版本上使用 innodb_analyze_is_persistent引數控制。
索引碎片處理
B-Tree索引隨著表資料的修改變化,包括插入、修改和刪除動作而導致碎片化,
這會降低查詢的效率。碎片化的索引資料可能會以無序的方式儲存在磁碟上,而如果葉子節點在物理分佈上是順序且緊密的,那查詢效能會更好。 對於支援optimize table命令的儲存引擎來說,可以通過此命令來重新整理表資料,或者通過匯出再匯入的方式重置資料。
對索引可以用刪除再重建的方式,也可以使用rebuild的方式。
對於不支援optimize table命令的儲存引擎,可以用alter table指定表修改為當前的引 擎來重建表 mysql > alter table table_name engine=<原來的儲存引擎>