也許你經常用MySQL
,也會經常用索引,但是對索引的原理和高級功能卻並不知道,我們在這裏一起學習下。
InnoDB
存儲索引
在數據庫中,如果索引太多,應用程序的性能可能會受到影響;如果索引太少,又會對查詢性能產生影響。所以,我們要追求兩者的一個平衡點,足夠多的索引帶來查詢性能提高,又不因爲索引過多導致修改數據等操作時負載過高。
InnoDB
支持3
種常見索引:
- 哈希索引
-
B+
樹索引 - 全文索引
我們接下來要詳細講解的就是B+
樹索引和全文索引。
哈希索引
InnoDB
存儲引擎支持的哈希索引是自適應的,會根據表的使用情況自動爲表生成哈希索引,不能人爲干預是否在一張表中生成哈希索引。這塊內容我們先不展開說,後續補充。
B+
樹索引
B+
樹索引是目前關係型數據庫系統中查找最爲常用和有效的索引,其構造類似於二叉樹,根據鍵值對快速找到數據。B+
樹(balance+ tree)
由B
樹(banlance tree 平衡二叉樹)
和索引順序訪問方法(ISAM: Index Sequence Access Method)
演化而來,這幾個都是經典的數據結構。而MyISAM
引擎最初也是參考ISAM
數據結構設計的。
基礎數據結構
想要了解B+
樹數據結構,我們先了解一些基礎的知識。
二分查找法
又稱爲折半查找法,指的是將數據順序排列,通過每次和中間值比較,跳躍式查找,每次縮減一半的範圍,快速找到目標的算法。其算法複雜度爲log2(n)
,比順序查找要快上一些。
如圖所示,從有序列表中查找48
,只需要3
步:
詳細的算法可以參考二分查找算法。
二叉查找樹
二叉查找樹的定義是在一個二叉樹中,左子樹的值總是小於根鍵值,根鍵值總是小於右子樹的值。在我們查找時,每次都從根開始查找,根據比較的結果來判斷繼續查找左子樹還是右子樹。其查找的方法非常類似於二分查找法。
平衡二叉樹
二叉查找樹的定義非常寬泛,可以任意構造,但是在極端情況下查詢的效率和順序查找一樣,如只有左子樹的二叉查找樹。
若想構造一個性能最大的二叉查找樹,就需要該樹是平衡的,即平衡二叉樹(由於其發明者爲G. M. Adelson-Velsky
和 Evgenii Landis
,又被稱爲AVL
樹)。其定義爲必須滿足任何節點的兩個子樹的高度最大差爲1
的二叉查找樹。平衡二叉樹相對結構較優,而最好的性能需要建立一個最優二叉樹,但由於維護該樹代價高,因此一般平衡二叉樹即可。
平衡二叉樹查詢速度很快,但在樹發生變更時,需要通過一次或多次左旋和右旋來達到樹新的平衡。這裏不發散講。
B+
樹
瞭解了基礎的數據結構後,我們來看下B+
樹的實現,其定義十分複雜,目標是爲磁盤或其他直接存取輔助設備設計的一種平衡查找樹。在該樹中,所有的記錄都按鍵值的大小放在同一層的葉子節點上,各葉子節點之間有指針進行連接(非連續存儲),形成一個雙向鏈表。索引節點按照平衡樹的方式構造,並存在指針指向具體的葉子節點,進行快速查找。
下面的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);
參考資料
- 二分查找算法:https://juejin.im/post/5c90ed...
- MySQL亂碼的原因和設置UTF8數據格式: https://segmentfault.com/a/11...
- 《MySQL技術內幕 InnoDB存儲引擎 第2版》第5章:索引與算法