InnoDB MySQL 全文索引 学习笔记
最近在學習MySQL全文索引的知識,基本搞清了功能以及使用方式,整理了相關資料分享出來一起學習進步哈
目錄
1. 倒排索引
2. InnoDB全文檢索
3.?MySQL全文檢索
3.1 Natural Language (自然語言模式)
3.2 Boolean(布爾模式)
3.3 Query Expansion(查詢擴展)
4. ngram 分詞器
5. 參考資料
全文檢索(Full-Text Search)是將存儲于數據庫中的整本書或整篇文章中的任意內容信息查找出來的技術。它可以根據需要獲得全文中有關章、節、段、句、詞等信息,也可以進行各種統計和分析。
1. 倒排索引
在老版本的MySQL數據庫中,InnoDB存儲引擎并不支持全文檢索技術,從InnoDB 1.2.x版本開始,InnoDB存儲引擎開始支持全文檢索,其支持MyISAM存儲引擎的全部功能,并且還支持其他的一些特性,在介紹其使用之前,先介紹下倒排索引(invertd index)。
全文檢索通常使用倒排索引(invertd index)來實現。倒排索引同B+樹索引一樣,也是一種索引結構。它在輔助表(auxiliary table)中存儲了單詞與單詞自身在一個或多個文檔中所在位置之間的映。這通常利用關聯數組實現,其擁有兩種表現形式:
inverted file index, 其表現形式為 {單詞,單詞所在文檔的ID}
full inverted index, 其表現形式為 {單詞,(單詞所在文檔的ID,在具體文檔中的位置)},如下圖這個例子,表格中存儲表t的內容
| DocumentId | Text | DocumentId | Text |
| 1 | Pease porridge hot, pease porridge cold | 4 | Some like it hot, some like it cold |
| 2 | Pease porridge in the pot | 5 | Some like it in the pot |
| 3 | Nine days old | 6 | Nine days old |
DocumentId 表示進行全文檢索文檔的Id,Text表示存儲的內容,用戶需要對存儲的這些文檔內容進行全文檢索。例如,查找出現過Some 單詞的文檔Id, 又或者查找單個文檔中出現過兩個Some單詞的文檔Id,等等。
對于 inverted file index 的關聯數組,其存儲的內容如下所示。
| Number | Text | Documents | Number | Text | Documents |
| 1 | cold | 1,4 | 8 | old | 3,6 |
| 2 | days | 3,6 | 9 | pease | 1,2 |
| 3 | hot | 1,4 | 10 | porridge | 1,2 |
| 4 | in | 2,5 | 11 | pot | 2,5 |
| 5 | it | 4,5 | 12 | some | 4,5 |
| 6 | like | 4,5 | 13 | the | 2,5 |
| 7 | nine | 3,6 | ? | ? | ? |
可以看到單詞cold存在于文檔1和4中,單詞 days 存在于文檔 3 和 6 中。之后再要進行全文查詢就簡單了。可以直接根據 Documents 得到包含查詢關鍵字的文檔。對于 inverted file index,其僅存取文檔 Id,而 full inverted index 存儲的是一對 (pair),即(DocumentId, Position), 因此其存儲的倒排索引如下表所示:
| Number | Text | Documents | Number | Text | Documents |
| 1 | cold | (1:6),(4:8) | 8 | old | (3:3),(6:3) |
| 2 | days | (3:2),(6:2) | 9 | pease | (1:1,4),(2:1) |
| 3 | hot | (1:3),(4:4) | 10 | porridge | (1:2,5),(2:2) |
| 4 | in | (2:3),(5:4) | 11 | pot | (2:5),(5:6) |
| 5 | it | (4:3, 7),(5:3) | 12 | some | (4:1,5),(5:1) |
| 6 | like | (4:2,6),(5:2) | 13 | the | (2:4),(5:5) |
| 7 | nine | (3:!),(6:1) | ? | ? | ? |
?full inverted index 還存儲了單詞所在的位置信息,如 code 這個單詞出現在 (1:6),即文檔 1 的第 6 個單詞為code。相比之下, full? inverted index 占用更多的空間,但是能更好地定位數據,并擴充一些其他的搜索特性。
2. InnoDB全文檢索
InnoDB存儲引擎從 1.2.x 版本開始支持全文檢索的技術,其采用?full inverted index 的方式。在 InnoDB存儲引擎中,將 (DocumentId, Position)視為一個 "ilist"。因此在全文檢索的表中,有兩個列,一個是word字段,另一個是 ilist字段, 并且在word字段上設有索引。此外,由于 InnoDB 存儲引擎在 ilist 字段中存放了 Position 信息,故可以進行 Proximity Search(鄰近檢索),而MyISAM 存儲引擎不支持該特性,下文給出鄰近檢索的例子。
正如之前所說的那樣,倒排索引需要將 word 字段存放到一張表中,這個表稱為 Auxiliary Table ( 輔助表 )。在 InnoDB 存儲引擎中,為了提高全文檢索的并行性能,共有 6 張 Auxiliary Table,目前每張表根據 word 的 Latin 編碼進行分區。
Auxiliary Table 是持久的表,存放于磁盤上。然而在 InnoDB 存儲引擎的全文索引中,還有另外一個重要的概念 FTS Index Cache( 全文檢索索引緩存表 ),其用來提高全文檢索的性能。
FTS Index Cache 是一個紅黑樹結構,其根據( word, ilist) 進行排序。這意味著插入的數據已經更新了對應的表,但是對全文索引的更新可能在分詞操作后還在?FTS Index Cache 中,Auxiliary Table 可能還沒有更新。InnoDB 存儲引擎會批量對?Auxiliary Table? 進行更新,而不是每次插入后更新一次Auxiliary Table 。當對全文檢索進行查詢時,Auxiliary Table? 首先會將在FTS Index Cache? 中對應的word 字段合并到?Auxiliary Table 中,然后再進行查詢。這種merge 操作類似于 Insert Buffer 功能,不同的是 Insert Buffer 是一個持久的對象,并且其是一個B+ 樹的結構。然而?FTS Index Cache 的作用又和?Insert Buffer? 類似,它提高了InnoDB 存儲引擎的性能,并且由于其根據紅黑樹排序后進行批量插入,其產生的?Auxiliary Table 相對較小。
Innodb 存儲引擎允許用戶查看指定倒排索引的Auxiliary Table中分詞的信息,可以通過設置參數?innodb_ft_aux_table 來觀察倒排索引的?Auxiliary Table。下面的SQL語句設置查看 test 數據庫中 fts_a 表的Auxiliary Table:
SET GLOBAL innodb_ft_aux_table="test/fts_a";在上述設置完成后,就可以通過查詢?information_schema 庫中的表?INNODB_FT_INDEX_TABLE 得到表 fts_a 中的分詞信息。?
對于InnoDB 存儲引擎而言,其總是在事務提交時將分詞寫入到 FTS Index Cache,然后再通過批量更新寫入到磁盤。雖然 InnoDB 存儲引擎通過一種延時的、批量的寫入方式來提高數據庫的性能,但是上述操作僅在事務提交時發生。
當數據庫關閉時,在 FTS Index Cache 中的數據庫會同步到磁盤上的??Auxiliary Table 中。然而,如果當數據庫發生宕機時,一些?FTS Index Cache 中的數據庫可能未被同步到磁盤上。那么下次重啟數據庫時,當用戶對表進行全文檢索( 查詢或者插入操作)時,InnoDB 存儲引擎會自動讀取未完成的文檔,然后進行分詞操作,再將分詞的結果放入到?FTS Index Cache 中。
參數 innodb_ft_cache_size 用來控制?FTS Index Cache 的大小,默認值為32M。當該緩存滿時,會將其中的(word,ilist)分詞信息同步到磁盤的?Auxiliary Table中。增大該參數可以提高全文檢索的性能,但是在宕機時,未同步到磁盤中的索引信息可能需要更長的時間進行恢復。
FTS Document ID 是另外一個重要的概念。在 InnoDB 存儲引擎中,為了支持全文檢索,必須有一個列與 word進行映射,在InnoDB 中這個列被命名為 FTS_DOC_ID,其類型必須是 BIGINT UNSIGNED NOT NULL,并且 InnoDB 存儲引擎自動會在該列上加入一個名為 FTS_DOC_ID_INDEX 的 Unique Index。上述這些操作都由 InnoDB 存儲引擎自己完成,用戶也可以在建表時自動添加FTS_DOC_ID,以及相應的Unique Index。由于列名為FTS_DOC_ID 的列具有特殊意義,因此創建時必須注意相應的類型,否則MySQL 數據庫會拋出錯誤,如下圖
文檔中分詞的插入操作是在事務提交時完成,然而對于刪除操作,其在事務提交時,不刪除磁盤??Auxiliary Table 中的記錄,而只是刪除??FTS Index Cache 中的記錄,并將其保存在 DELETED auxiliary table 中。在設置參數?innodb_ft_aux_table 后,用戶同樣可以訪問?information_schema 庫下的表 INNODB_FT_DELETED 來觀察刪除的 FTS Document ID。
由于文檔的DML 操作實際并不刪除索引中的數據,相反還會在對應的DELETED 表中插入記錄,因此隨著應用程序的允許,索引會變得非常大,即使索引中的有些數據已經被刪除,查詢也不會選擇這類記錄。為此,InnoDB 存儲引擎提供了一種方式,允許用戶手動地將已經存儲的記錄從索引中徹底刪除,該命令是 optimize table。因為 optimize table 還會進行一些其他的操作,如 Cardinality 的重新統計,若用戶希望僅對倒排索引進行操作,那么可以通過參數 innodb_optimize_fulltext_only進行設置,如:
set global innodb_optimize_fulltext_only=1;optimize table fts_a;若被刪除的文檔非常多,那么?optimize table 操作可能需要占用非常多的時間,這會影響影響應用程序的并發性,并極大地降低用戶的響應時間。用戶可以參數 innodb_ft_num_word_optimize 來限制每次實際刪除的分詞數量。該參數的默認值為2000。
下面通過例子來觀察,首先通過如下代碼創建表 fts_a:
create table fts_a (FTS_DOC_ID bigint unsigned auto_increment not null,body text,primary key(FTS_DOC_ID));insert into fts_a select null, 'Pease porridge in the pot'; insert into fts_a select null, 'Pease porridge hot, pease porridge cold'; insert into fts_a select null, 'Nine days old'; insert into fts_a select null, 'Some like it hot, some like it cold'; insert into fts_a select null, 'Some like it in the pot'; insert into fts_a select null, 'Nine days old'; insert into fts_a select null, 'I like code days'; create fulltext index idx_fts on fts_a(body);上述代碼創建了表 fts_a,由于body字段是進行全文檢索的字段,因此創建一個類型為 FULLTEXT的索引。這里首先導入數據,然后再進行倒排索引的創建,這也是比較推薦的一張方式,創建完成后觀察表中的數據:
通過設置?innodb_ft_aux_table 來查看分詞對應的信息:
SET GLOBAL innodb_ft_aux_table="study/fts_a";select * from information_schema.INNODB_FT_INDEX_TABLE;可以看到每個word都對應了一個DOC_ID 和 POSITION。此外,還記錄了FIRST_DOC_ID、LAST_DOC_ID以及 DOC_COUNT,分別代表了該 word 第一次出現的文檔ID,最后一次出現的文檔ID,以及該word在多少個文檔中存在。若執行如下語句,會刪除FTS_DOC_ID 為7 的文檔:
delete from fts_a where FTS_DOC_ID=7;由于之前的介紹,InnoDB 存儲引擎并不會直接刪除索引中對應的記錄,而是將刪除的文檔ID插入到DELETED表,因此可以進行如下的查詢:
select * from information_schema.INNODB_FT_DELETED;可以看到刪除的文檔ID插入到了表 INNODB_FT_DELETED中,若用戶想要徹底刪除倒排索引中該文檔的分詞信息,那么可以運行如下的SQL語句:
set global innodb_optimize_fulltext_only=1; optimize table fts_a; select * from information_schema.INNODB_FT_BEING_DELETED;通過上面的例子可看到,運行命令 optimize table 可將記錄進行徹底的刪除,并且徹底刪除的文檔ID會記錄到表?INNODB_FT_BEING_DELETE中,此外,由于7這個文檔ID已經被刪除,因此不允許再次插入這個文檔ID,否則數據庫會拋出錯誤。
接下來介紹下 stopword 列表( 停用詞 ),其表示該列表中的word 不需要對其進行索引分詞操作。例如,對于the 這個單詞,由于其不具有具體的意義,因此將其視為 stopword。InnoDB 存儲引擎有一張默認的 stopword 列表,其在?information_schema下,表名為?INNODB_FT_DEFAULT_STOPWORD,默認有36個stopword。此外用戶也可以通過參數 innodb_ft_server_stopword_table 來自定義stopword 列表,如下:
// 查看默認停用詞表 SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;// 自定義停用詞表 create table user_stopword(value VARCHAR(30)) ENGINE= INNODB;set global innodb_ft_server_stopword_table = "test/user_stopword";使用Innodb 存儲引擎的全文檢索還存在以下的限制:
3.?MySQL全文檢索
介紹完了InnoDB 的全文檢索,接下來看下MySQL的全文檢索。
MySQL數據庫支持全文檢索(Full-Text Search)的查詢,其語法為:
MATCH (col1, col2,...) AGAINST (expr [search_modifier])search_modifier: {IN NATURAL LANGUAGE MODE| IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION| IN BOOLEAN MODE| WITH QUERY EXPANSION }MySQL 數據庫通過 MATCH()···AGAINST()語法支持全文檢索的查詢,MATCH 指定了需要被查詢的列,AGAINST指定了使用何種方法去進行查詢。下面將對各種查詢模式進行詳細的介紹。
3.1 Natural Language (自然語言模式)
全文檢索通過MATCH函數進行查詢,默認采用 Natural Language 模式,其表示查詢帶有指定word的文檔,對于上文創建的表fts_a,查詢body字段中帶有Pease 的文檔,若不使用全文索引技術,則允許使用如下語句:
select * from fts_a where like '%Pease%'顯然該語句不能使用B+樹索引,若采用全文檢索技術,可以使用下面的SQL語句進行查詢:
select * from fts_a where match(body) against ('porridge' in natural language mode);由于NATURAL LANGUAGE MODE 是默認的全文檢索查詢模式,因此可以省略查詢修飾符,即上述語句可以寫為:
select * from fts_a where match(body) against ('porridge');在where條件中使用MATCH函數,查詢返回的結果是根據相關性(Relevance)進行降序排序的,即相關性最高的結果放在第一位。相關性的值是一個非負的浮點數字,0表示沒有任何相關性。根據MySQL官方文檔可知,其相關性的計算依據以下四個條件:
對于上述查詢,由于 Porridge 在文檔2中出現了2次,因而具有更高的相關性,故第一個顯示。MySQL相關性的計算公式可以查詢官方文檔得到,這里不再詳述。
用戶可以通過如下SQL語句查看相關性:
select fts_doc_id, body, match(body) against ('Porridge' in natural language mode) as Relevance from fts_a;?對于InnoDB 存儲引擎的全文檢索,還需要考慮一下的因素:
如果詞在stopword 中,則不對該詞進行查詢。參數?innodb_ft_min_token_size和?innodb_ft_max_token_size 控制InnoDB 存儲引擎查詢字符的長度,當長度小于innodb_ft_min_token_size 或者 大于innodb_ft_max_token_size 時,會忽略該詞的搜索。在InnoDB 存儲引擎中,參數?innodb_ft_min_token_size 默認值為3,參數?innodb_ft_max_token_size 默認值為84。
3.2 Boolean(布爾模式)
MySQL數據庫允許使用 IN BOOLEAN MODE 修飾符來進行全文檢索。當使用該修飾符時,查詢字符串的前后字符會有特殊的含義,例如下面的語句要求查詢有字符串 Pease 但沒有 hot 的文檔,其中 + 和 - 分別表示這個單詞必須出現,或者一定不存在。
select * from fts_a where match(body) against ('+Pease -hot' in boolean mode)\G;Boolean 全文檢索支持一下幾種操作符:
| 操作符 | 含義 |
| + | 表示該word必須存在 |
| - | 表示該word必須被排除 |
| (空) | 表示該word是可選的,但是如果出現,其相關性會更高 |
| @distance | 表示查詢的多個單詞之間的距離是否在distance之內,distance的單位是詞。這種全文檢索的查詢也稱為Proximity Search。如MATCH (body) AGAINST (' "Peace pot"@30 in boolean mode) 表示字符串Peace 和 pot 之間的距離需在30個詞以內。 |
| > | 表示出現該單詞時增加相關性 |
| < | 表示出現該單詞時降低相關性 |
| ~ | 表示允許出現該單詞,但是出現時相關性為負(全文檢索查詢允許負相關性) |
| * | 表示以該單詞開頭的單詞,如lik*,表示可以是lik、like,又或者是likes。 |
| " | 表示短語 |
下面展示每個操作符對應的例子:
1. 查找既有 pease 又有 hot 的文檔:
select * from fts_a where match(body) against ('+pease +hot' in boolean mode);2. 查找只有pease 但沒有 hot 的文檔
select * from fts_a where match(body) against ('+pease -hot' in boolean mode);3. 查找有pease 或 hot 的文檔
select * from fts_a where match(body) against ('pease hot' in boolean mode);?4. Proximity Search 臨近查找
select * from fts_a where match(body) against (' "hot cole"@4' in boolean mode); select * from fts_a where match(body) against (' "hot cole"@3' in boolean mode);hot 和 cole 之間距離有{, pease porridge cole} 四個詞,因此后一語句不滿足條件。
5. > 增加相關性
select fts_doc_id, body, MATCH(body) AGAINST ('like >pot' in boolean mode) AS Relevance from fts_a;上述SQL語句查詢根據是否有單詞like 或 pot 進行相關性統計,并且出現單詞pot后相關性需要增加。文檔4雖然有兩個like單詞,但是沒有pot,因此相關性沒有1和5高。
6. < 降低相關性
下面的查詢增加了 '<some' 的條件得到的結果:
select fts_doc_id, body, MATCH(body) AGAINST ('like >hot <some' in boolean mode) AS Relevance from fts_a;可以發現文檔5的相關性變為了負,這是因為雖然其中存在like單詞,但是也存在some單詞,所以根據查詢條件,其相關性變為了負相關。
7. * 通配符
select * from fts_a where MATCH(body) AGAINST ('po*' in boolean mode);可以看到最后結果中的文檔包含以po開頭的單詞。
8. 短語查詢
select * from fts_a where MATCH(body) AGAINST('like it hot' in boolean mode); select * from fts_a where MATCH(body) AGAINST('"like it hot"' in boolean mode);可以看到第一條SQL語句沒有使用 ""將 like it hot 視為一個短語,而只是將其視為3個單詞,因此返回4個結果,而第二條SQL語句使用"likt it hot",因此查詢的是短語,故僅文檔4符合查詢條件。
3.3 Query Expansion(查詢擴展)
MySQL數據庫還支持全文檢索的擴展查詢。這種查詢通常在查詢的關鍵詞太短,用戶需要implied knowledge(隱含知識)時進行。例如,對于單詞database的查詢,用戶可能希望查詢的不僅僅是包含database的文檔,可能還指那些包含MySQL、oracle、DB2單詞。而這時可以使用Query Expansion模式開啟全文檢索的implied knowledge。
通過在查詢短語中添加 WITH QUERY EXPANSION 或 IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION 可以開啟blind query expansion(又稱為automatic relevance feedback)。該查詢分為兩個階段:
下面結合一個例子來說明,首先建立測試表:
create table articles(id int unsigned auto_increment not null primary key,title VARCHAR(200),body text,fulltext(title, body))engine=InnoDB;insert into articles (title, body) values('MySQL Tutorial', 'DBMS stands for DataBase ...'),('How To Use MySQL Well', 'After you went through a ...'),('Optimizing MySQL', 'In this turitual we will show ...'),('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),('MySQL vs. YourSQL','In the following database comparision ...'),('MySQL Security','When configured properly, MySQL ...'),('Tuning DB2','For IBM database ...'),('IBM History','DB2 history for IBM ...');在這個例子中,并沒有顯示創建FTS_DOC_ID列,因此InnoDB存儲引擎會自動建立該列,并添加唯一索引。此外,表articles 的全文檢索索引是根據列title 和 body 的聯合索引。接著根據database 關鍵字進行的全文索引查詢:
select * from articles where match(title, body) against ('database');可以看到,查詢返回了3條記錄,body字段包含database關鍵字。接著開啟Query Expansion,觀察結果:
select * from articles where match(title, body) against ('database' with query expansion);可以看到最后得到8條結果,除了之前包含的 database的記錄,也有包含title 或 body 字段中包含MySQL、DB2的文檔,這就是Query Expansion。
由于Query Expansion 的全文檢索可能帶來許多非相關性的查詢,因此在使用時,用戶可能需要非常謹慎。
4. ngram 分詞器
前面我們說過,InnoDB存儲引擎內置的分詞器不支持中文、日文等語言,因為這些語言并不使用空格作為分詞符,為了解決這個問題,MySQL提供了ngram全文解析器。自MySQL5.7.6版起,MySQL將ngram全文解析器作為內置的服務器插件,這意味著當MySQL數據庫服務器啟動時,MySQL會自動加載該插件。 MySQL支持用于InnoDB和MyISAM存儲引擎的ngram全文解析器。
根據定義,ngram是來自文本序列的多個字符的連續序列。 ngram全文解析器的主要功能是將文本序列標記為n個字符的連續序列。
以下說明了ngram全文解析器如何標記不同值n的文本序列:
n = 1: '全','文','檢','索' n = 2: '全文', '文檢', '檢索' n = 3: '全文檢', '文檢索' n = 4: '全文檢索'要創建使用ngram全文解析器的FULLTEXT索引,可以在CREATE TABLE,ALTER TABLE或CREATE INDEX語句中添加WITH PARSER ngram。
CREATE TABLE ft_chn (id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(255),body TEXT,FULLTEXT ( title , body ) WITH PARSER NGRAM ) ENGINE=INNODB CHARACTER SET UTF8;值得注意的是,ngram的自然語言搜索模式中,搜索項被轉換為ngram值的并集。在ngram布爾模式中,搜索項被轉換成ngram短語搜索。
默認情況下,ngram中的令牌大小(n)為2,要更改令牌大小,請使用ngram_token_size配置選項,這是個只讀選項,有兩種配置方式,值的范圍是:1到10,一種是啟動時傳參,另一種是寫到配置文件里:
// 啟動時傳參 mysqld --ngram_token_size=1// 寫到配置文件里 [mysqld] ngram_token_size=1MySQL全文索引的基本知識就總結到這里。
?
5. 參考資料
1. 《MySQL技術內幕 InnoDB存儲引擎第二版》姜承堯著
2.? MySQL ngram全文解析器
3. 關于倒排索引討論
4.?倒排索引
5.?MySQL · 引擎特性 · InnoDB 全文索引簡介
================================================================================================
Linux應用程序、內核、驅動、后臺開發交流討論群(745510310),感興趣的同學可以加群討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。...
?
?
總結
以上是生活随笔為你收集整理的InnoDB MySQL 全文索引 学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lenovo昭阳e40怎么进bios l
- 下一篇: Redis 有序集合(zset)取交集(