1. 程式人生 > 實用技巧 >MySQL資料庫索引

MySQL資料庫索引

一、資料庫索引介紹

索引是一種特殊的檔案(MySql資料表上的索引是表空間的一個組成部分),它們包含著對資料表裡所有記錄的引用指標,直接在索引中查詢符合條件的選項,加快資料庫的查詢速度,而不是一行一行去遍歷資料後才選擇出符合條件的。如果沒有索引,執行查詢時MySQL必須從第一個記錄開始掃描整個表的所有記錄,直至找到符合要求的記錄。表裡面的記錄數量越多,這個操作的代價就越高。如果作為搜尋條件的列上已經建立了索引,MySQL無需掃描任何記錄即可迅速得到目標記錄所在的位置。

索引的本質是什麼?索引有什麼優點,缺點是什麼?

索引是幫助MySQL高效獲取資料的資料結構。因此,索引的本質是一種資料結構。
在資料之外,資料庫系統還可以維護滿足特定查詢演算法的資料結構,這些資料結構以某種方式指向真實資料,這樣就可以在這些資料結構上實現高階查詢演算法,這種資料結構就是索引。


優點:

1、提高資料檢索效率,降低資料庫的IO成本;
2、通過索引對資料進行排序,降低了資料排序的成本,降低了CPU的利用率;

缺點:

1、索引實際上也是一張表,索引會佔用一定的儲存空間;
2、更新資料表的資料時,需要同時維護索引表,因此,會降低insert、update、delete的速度;

二、MySQL索引型別包括哪些?

1、普通索引

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

◆ 建立索引

CREATE INDEX indexName ON mytable(username(length)); 

如果是CHAR,VARCHAR型別,length可以小於欄位實際長度;如果是BLOB和TEXT型別,必須指定 length,下同。

◆ 修改表結構

ALTER mytable ADD INDEX [indexName] ON (username(length)) 

◆ 建立表的時候直接指定

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
INDEX [indexName] (username(length))  
);  

刪除索引的語法:

DROP INDEX [indexName] ON mytable; 

2、唯一索引

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

◆建立索引

CREATE UNIQUE INDEX indexName ON mytable(username(length)) 

◆修改表結構

ALTER mytable ADD UNIQUE [indexName] ON (username(length)) 

◆建立表的時候直接指定

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
UNIQUE [indexName] (username(length))  
);  

3、主鍵索引

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

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
PRIMARY KEY(ID)  
);  

當然也可以用 ALTER 命令。記住:一個表只能有一個主鍵。

4、組合索引

為了形象地對比單列索引和組合索引,為表新增多個欄位:

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
city VARCHAR(50) NOT NULL,  
age INT NOT NULL 
);  

為了進一步榨取MySQL的效率,就要考慮建立組合索引。就是將 name, city, age建到一個索引裡:

ALTER TABLE mytable ADD INDEX name_city_age (name(10),city,age); 

建表時,usernname長度為 16,這裡用 10。這是因為一般情況下名字的長度不會超過10,這樣會加速索引查詢速度,還會減少索引檔案的大小,提高INSERT的更新速度。

如果分別在 usernname,city,age上建立單列索引,讓該表有3個單列索引,查詢時和上述的組合索引效率也會大不一樣,遠遠低於我們的組合索引。雖然此時有了三個索引,但MySQL只能用到其中的那個它認為似乎是最有效率的單列索引。

建立這樣的組合索引,其實是相當於分別建立了下面三組組合索引:

usernname,city,age  
usernname,city  
usernname  

為什麼沒有 city,age這樣的組合索引呢?這是因為MySQL組合索引“最左字首”的結果。簡單的理解就是隻從最左面的開始組合。並不是只要包含這三列的查詢都會用到該組合索引,下面的幾個SQL就會用到這個組合索引:

SELECT * FROM mytable WHREE username="admin" AND city="鄭州" 
SELECT * FROM mytable WHREE username="admin" 

而下面幾個則不會用到:

SELECT * FROM mytable WHREE age=20 AND city="鄭州" 
SELECT * FROM mytable WHREE city="鄭州" 

三、 InnoDB儲存索引

在資料庫中,如果索引太多,應用程式的效能可能會受到影響;如果索引太少,又會對查詢效能產生影響。所以,我們要追求兩者的一個平衡點,足夠多的索引帶來查詢效能提高,又不因為索引過多導致修改資料等操作時負載過高。

InnoDB支援3種常見索引:

 ● 雜湊索引

 ● B+ 樹索引

 ● 全文索引

我們接下來要詳細講解的就是B+ 樹索引和全文索引。

雜湊索引

學習雜湊索引之前,我們先了解一些基礎的知識:雜湊演算法。雜湊演算法是一種常用的演算法,時間複雜度為O(1)。它不僅應用在索引上,各個資料庫應用中也都會使用。

雜湊表

雜湊表(Hash Table)也稱散列表,由直接定址表改進而來。

在該表中U表示關鍵字全集,K表示實際存在的關鍵字,右邊的陣列(雜湊表)表示在記憶體中可以直接定址的連續空間,雜湊表中每個插槽關聯的單向連結串列中儲存實際資料的真實地址。

如果右邊的陣列直接使用直接定址表,那麼對於每一個關鍵字K都會存在一個h[K]且不重複,這樣存在一些問題,如果U資料量過大,那麼對於計算機的可用容量來說有點不實際。而如果集合K佔比U的比例過小,則分配的大部分空間都要浪費。

因此我們使用雜湊表,我們通過一些函式h(k)來確定對映關係,這樣讓離散的資料儘可能均勻分佈的利用陣列中的插槽,但會有一個問題,多個關鍵字對映到同一個插槽中,這種情況稱為碰撞(collision),資料庫中採用最簡單的解決方案:連結法(chaining)。也就是每個插槽儲存一個單項鍊表,所有碰撞的元素會依次形成連結串列中的一個結點,如果不存在,則連結串列指向為NULL。

而使用的函式h(k)成為雜湊函式,它必須能夠很好的進行雜湊。最好能夠避免碰撞或者達到最小碰撞。一般為了更好的處理雜湊的關鍵字,我們會將其轉換為自然數,然後通過除法雜湊、乘法雜湊或者全域雜湊來實現。資料庫一般使用除法雜湊,即當有m個插槽時,我們對每個關鍵字k進行對m的取模:h(k) = k % m。

InnoDB儲存引擎中的雜湊演算法

InnoDB儲存引擎使用雜湊演算法來查詢字典,衝突機制採用連結串列,雜湊函式採用除法雜湊。對於緩衝池的雜湊表,在快取池中的每頁都有一個chain指標,指向相同雜湊值的頁。對於除法雜湊,m的值為略大於2倍緩衝池頁數量的質數。如當前innodb_buffer_pool_size大小為10M,則共有640個16KB的頁,需要分配1280個插槽,而略大於的質數為1399,因此會分配1399個槽的雜湊表,用來雜湊查詢緩衝池中的頁。

而對於將每個頁轉換為自然數,每個表空間都有一個space_id,使用者要查詢的是空間中某個連續的16KB的頁,即偏移量(offset),InnoDB將space_id左移20位,再加上space_id和offset,即K=space_id<<20+space_id+offset,然後使用除法雜湊到各個槽中。

自適應雜湊索引

自適應雜湊索引採用上面的雜湊表實現,屬於資料庫內部機制,DBA不能干預。它只對字典型別的查詢非常快速,而對範圍查詢等卻無能為力,如:

select * from t where f='100';

我們可以檢視自適應雜湊索引的使用情況:

mysql> show engine innodb status\G;
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2019-05-13 23:32:21 7f4875947700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 32 seconds
...
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1226, seg size 1228, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 276671, node heap has 1288 buffer(s)
0.16 hash searches/s, 16.97 non-hash searches/s

我們可以看到自適應雜湊的使用情況,可以通過最後一行的hash searches/non-hash searches來判斷使用雜湊索引的效率。

我們可以使用innodb_adaptive_hash_index引數來禁用或啟用此特性,預設開啟。

B+ 樹索引

B+ 樹索引是目前關係型資料庫系統中查詢最為常用和有效的索引,其構造類似於二叉樹,根據鍵值對快速找到資料。B+ 樹(balance+ tree)由B樹(banlance tree 平衡二叉樹)和索引順序訪問方法(ISAM: Index Sequence Access Method)演化而來,這幾個都是經典的資料結構。而MyISAM引擎最初也是參考ISAM資料結構設計的。

基礎資料結構

想要了解B+ 樹資料結構,我們先了解一些基礎的知識。

(1)二分查詢法

又稱為折半查詢法,指的是將資料順序排列,通過每次和中間值比較,跳躍式查詢,每次縮減一半的範圍,快速找到目標的演算法。其演算法複雜度為log2(n),比順序查詢要快上一些。

如圖所示,從有序列表中查詢48,只需要3步:

詳細的演算法可以參考二分查詢演算法。

(2)二叉查詢樹

二叉查詢樹的定義是在一個二叉樹中,左子樹的值總是小於根鍵值,根鍵值總是小於右子樹的值。在我們查詢時,每次都從根開始查詢,根據比較的結果來判斷繼續查詢左子樹還是右子樹。其查詢的方法非常類似於二分查詢法。

(3)平衡二叉樹

二叉查詢樹的定義非常寬泛,可以任意構造,但是在極端情況下查詢的效率和順序查詢一樣,如只有左子樹的二叉查詢樹。

若想構造一個性能最大的二叉查詢樹,就需要該樹是平衡的,即平衡二叉樹(由於其發明者為G. M. Adelson-Velsky 和 Evgenii Landis,又被稱為AVL樹)。其定義為必須滿足任何節點的兩個子樹的高度最大差為1的二叉查詢樹。平衡二叉樹相對結構較優,而最好的效能需要建立一個最優二叉樹,但由於維護該樹代價高,因此一般平衡二叉樹即可。

平衡二叉樹查詢速度很快,但在樹發生變更時,需要通過一次或多次左旋和右旋來達到樹新的平衡。這裡不發散講。

B+ 樹

瞭解了基礎的資料結構後,我們來看下B+ 樹的實現,其定義十分複雜,簡單來說就是在B樹上增加規定:

1、葉子結點存資料,非葉子結點存指標

2、所有葉子結點從左到右用雙向連結串列記錄

目標是為磁碟或其他直接存取輔助裝置設計的一種平衡查詢樹。在該樹中,所有的記錄都按鍵值的大小放在同一層的葉子節點上,各葉子節點之間有指標進行連線(非連續儲存),形成一個雙向連結串列。索引節點按照平衡樹的方式構造,並存在指標指向具體的葉子節點,進行快速查詢。

下面的B+ 樹為資料較少時,此時高度為2,每頁固定存放4條記錄,扇出固定為5(圖上灰色部分)。葉子節點存放多條資料,是為了降低樹的高度,進行快速查詢。

當我們插入28、70、95 3條資料後,B+ 樹由於資料滿了,需要進行頁的拆分。此時高度變為3,每頁依然是4條記錄,雙向連結串列未畫出但是依然是存在的,現在可以看出來是一個平衡二叉樹的雛形了。

InnoDB的B+ 樹索引

InnoDB的B+ 樹索引的特點是高扇出性,因此一般樹的高度為2~4層,這樣我們在查詢一條記錄時只用I/O 2~4次。當前機械硬碟每秒至少100次I/O/s,因此查詢時間只需0.02~0.04s。

資料庫中的B+ 樹索引分為聚集索引(clustered index)和輔助索引(secondary index)。它們的區別是葉子節點存放的是否為一整行的完整資料。

聚集索引

聚集索引就是按照每張表的主鍵(唯一)構造一棵B+ 樹,同時葉子節點存放整行的完整資料,因此將葉子節點稱為資料頁。由於定義了資料的邏輯順序,聚集索引也能快速的進行範圍型別的查詢。

聚集索引的葉子節點按照邏輯順序連續儲存,葉子節點內部物理上連續儲存,作為最小單元,葉子節點間通過雙向指標連線,物理儲存上不連續,邏輯儲存上連續。

聚集索引能夠針對主鍵進行快速的排序查詢和範圍查詢,由於是雙向連結串列,因此在逆序查詢時也非常快。

我們可以通過explain命令來分析MySQL資料庫的執行計劃:

# 查看錶的定義,可以看到id為主鍵,name為普通列
mysql> show create table dimensionsConf;
| Table          | Create Table    
| dimensionsConf | CREATE TABLE `dimensionsConf` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `remark` varchar(1024) NOT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `fullindex_remark` (`remark`)
) ENGINE=InnoDB AUTO_INCREMENT=178 DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)
 
# 先測試一個非主鍵的name屬性排序並查詢,可以看到沒有使用到任何索引,且需要filesort(檔案排序),這裡的rows為輸出行數的預估值
mysql> explain select * from dimensionsConf order by name limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 57
        Extra: Using filesort
1 row in set (0.00 sec)
 
# 再測試主鍵id的排序並查詢,此時使用主鍵索引,在執行計劃中沒有了filesort操作,這就是聚集索引帶來的優化
mysql> explain select * from dimensionsConf order by id limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 10
        Extra: NULL
1 row in set (0.00 sec)
 
# 再查詢根據主鍵id的範圍查詢,此時直接根據葉子節點的上層節點就可以快速得到範圍,然後讀取資料
mysql> explain select * from dimensionsConf where id>10 and id<10000\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 56
        Extra: Using where
1 row in set (0.00 sec)

輔助索引

輔助索引又稱非聚集索引,其葉子節點不包含行記錄的全部資料,而是包含一個書籤(bookmark),該書籤指向對應行資料的聚集索引,告訴InnoDB儲存引擎去哪裡查詢具體的行資料。輔助索引與聚集索引的關係就是結構相似、獨立存在,但輔助索引查詢非索引資料需要依賴於聚集索引來查詢。

全文索引

我們通過B+ 樹索引可以進行字首查詢,如:

select * from blog where content like 'xxx%';

只要為content列添加了B+ 樹索引(聚集索引或輔助索引),就可快速查詢。但在更多情況下,我們在部落格或搜尋引擎中需要查詢的是某個單詞,而不是某個單詞開頭,如:

select * from blog where content like '%xxx%';

此時如果使用B+ 樹索引依然是全表掃描,而全文檢索(Full-Text Search)就是將整本書或文章內任意內容檢索出來的技術。

倒排索引

全文索引通常使用倒排索引(inverted index)來實現,倒排索引和B+ 樹索引都是一種索引結構,它需要將分詞(word)儲存在一個輔助表(Auxiliary Table)中,為了提高全文檢索的並行效能,共有6張輔助表。輔助表中儲存了單詞和單詞在各行記錄中位置的對映關係。它分為兩種:

inverted file index(倒排檔案索引),表現為{單詞,單詞所在文件ID}
full inverted index(詳細倒排索引),表現為{單詞,(單詞所在文件ID, 文件中的位置)}
對於這樣的一個數據表:

倒排檔案索引型別的輔助表儲存為:

詳細倒排索引型別的輔助表儲存為,佔用更多空間,也更好的定位資料,比提供更多的搜尋特性:

全文檢索索引快取

輔助表是存在與磁碟上的持久化的表,由於磁碟I/O比較慢,因此提供FTS Index Cache(全文檢索索引快取)來提高效能。FTS Index Cache是一個紅黑樹結構,根據(word, list)排序,在有資料插入時,索引先更新到快取中,而後InnoDB儲存引擎會批量進行更新到輔助表中。

當資料庫宕機時,尚未落盤的索引快取資料會自動讀取並存儲,配置引數innodb_ft_cache_size控制快取的大小,預設為32M,提高該值,可以提高全文檢索的效能,但在故障時,需要更久的時間恢復。

在刪除資料時,InnoDB不會刪除索引資料,而是儲存在DELETED輔助表中,因此一段時間後,索引會變得非常大,可以通過optimize table命令手動刪除無效索引記錄。如果需要刪除的內容非常多,會影響應用程式的可用性,引數innodb_ft_num_word_optimize控制每次刪除的分詞數量,預設為2000,使用者可以調整該引數來控制刪除幅度。

全文檢索限制

全文檢索存在一個黑名單列表(stopword list),該列表中的詞不需要進行索引分詞,預設共有36個,如the單詞。你可以自行調整:

mysql> select * from information_schema.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
| be    |
| by    |
| com   |
| de    |
| en    |
| for   |
| from  |
| how   |
| i     |
| in    |
| is    |
| it    |
| la    |
| of    |
| on    |
| or    |
| that  |
| the   |
| this  |
| to    |
| was   |
| what  |
| when  |
| where |
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+
36 rows in set (0.00 sec)

其他限制還有:

 ● 每張表只能有一個全文檢索索引

 ● 多列組合的全文檢索索引必須使用相同的字符集和字元序,不瞭解的可以參考MySQL亂碼的原因和設定UTF8資料格式

 ● 不支援沒有單詞界定符(delimiter)的語言,如中文、日語、韓語等

全文檢索

我們建立一個全文索引:

mysql> create fulltext index fullindex_remark on dimensionsConf(remark);
Query OK, 0 rows affected, 1 warning (0.39 sec)
Records: 0  Duplicates: 0  Warnings: 1
 
mysql> show warnings;
+---------+------+--------------------------------------------------+
| Level   | Code | Message                                          |
+---------+------+--------------------------------------------------+
| Warning |  124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

全文檢索有兩種方法:

 ● 自然語言(Natural Language),預設方法,可省略:(IN NATURAL LANGUAE MODE)

 ● 布林模式(Boolean Mode):(IN BOOLEAN MODE)

自然語言還支援一種擴充套件模式,後面加上:(WITH QUERY EXPANSION)。

其語法為MATCH()...AGAINST(),MATCH指定被查詢的列,AGAINST指定何種方法查詢。

自然語言檢索

mysql> select remark from dimensionsConf where remark like '%baby%';
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)
 
mysql> select remark from dimensionsConf where match(remark) against('baby' IN NATURAL LANGUAGE MODE);
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)
 
# 檢視下執行計劃,使用了全文索引排序
mysql> explain select * from dimensionsConf where match(remark) against('baby');
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table          | type     | possible_keys    | key              | key_len | ref  | rows | Extra       |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
|  1 | SIMPLE      | dimensionsConf | fulltext | fullindex_remark | fullindex_remark | 0       | NULL |    1 | Using where |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
1 row in set (0.00 sec)

我們也可以檢視各行資料的相關性,是一個非負的浮點數,0代表沒有相關性:

mysql> select id,remark,match(remark) against('baby') as relevance from dimensionsConf;
+-----+-----------------------+--------------------+
| id  | remark                | relevance          |
+-----+-----------------------+--------------------+
| 106 | c                     |                  0 |
| 111 | 運營商             |                  0 |
| 115 | a baby like panda     | 2.1165735721588135 |
| 116 | a baby like panda     | 2.1165735721588135 |
+-----+-----------------------+--------------------+
4 rows in set (0.01 sec)

布林模式檢索

MySQL也允許用修飾符來進行全文檢索,其中特殊字元會有特殊含義:

● +: 該word必須存在
● -: 該word必須排除
● (no operator): 該word可選,如果出現,相關性更高
● @distance: 查詢的多個單詞必須在指定範圍之內
● >: 出現該單詞時增加相關性
● <: 出現該單詞時降低相關性
● ~: 出現該單詞時相關性為負
● *: 以該單詞開頭的單詞
● ": 表示短語

# 代表必須有a baby短語,不能有man,可以有lik開頭的單詞,可以有panda,
select remark from dimensionsConf where match(remark) against('+"a baby" -man lik* panda' IN BOOLEAN MODE);

擴充套件查詢

當查詢的關鍵字太短或不夠清晰時,需要用隱含知識來進行檢索,如database關聯的MySQL/DB2等。但這個我並沒太明白怎麼使用,後續補充吧。

類似的使用是:

select * from articles where match(title,body) against('database' with query expansion);

如果任何問題或者建議,歡迎留言交流。