InnoDB存儲引擎從1.2.x開始支持全文索引技術,其采用full inverted index的方式。在InnoDB存儲引擎中,將(DocumentID,Postition)視為一個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
為了支持全文檢索,必須有一個列與word進行映射。在InnoDB中這個列被命名成FTS_DOC_ID,其類型為BIGINT UNSIGNED NOT NULL,並且InnoDB存儲引擎自動會在該列加上一個名為FTS_DOC_ID_INDEX的Unique Index.這些操作由存儲引擎自己完成,用戶也可以在建表時自動添加FTS_DOC_ID,以及對應的Unique Index。由於列名FTS_DOC_ID聚友特殊意義,因此在創建時必須注意相應的類型,否則會報錯
可以看到,由於用戶手動定義FTS_DOC_ID為INT,而非BIGINT因此在創建時候會拋出異常,應該將此處修改成對應的BIGINT即可
文檔中的分詞的插入操作是在事務提交時完成,但是對於刪除操作,其在事務提交時,不刪除磁盤Auxiliary Table的記錄,而只是刪除FTS Cache Index記錄,對於Auxiliary Table中被刪除的記錄,存儲引擎會記錄其FTS DOCUMENT ID ,並將其保存在DELETE 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
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 post'; 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 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);
查看數據
mysql> select * from fts_a; +------------+----------------------------------------+ | FTS_DOC_ID | body | +------------+----------------------------------------+ | 1 | pease porridge in the post | | 2 | pease porridge hot,pease porridge cold | | 3 | Nine days old | | 4 | Some like it hot,some like it cold | | 5 | Some like it the pot | | 6 | Nine days old | | 7 | I like code days | +------------+----------------------------------------+ 7 rows in set (0.00 sec)
mysql> set global innodb_ft_aux_table='iot2/fts_a'; Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM information_schema.`INNODB_FT_INDEX_TABLE`;
+----------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+----------+--------------+-------------+-----------+--------+----------+
| code | 7 | 7 | 1 | 7 | 7 |
| cold | 2 | 4 | 2 | 2 | 34 |
| cold | 2 | 4 | 2 | 4 | 30 |
| days | 3 | 7 | 3 | 3 | 5 |
| days | 3 | 7 | 3 | 6 | 5 |
| days | 3 | 7 | 3 | 7 | 12 |
| hot | 2 | 4 | 2 | 2 | 15 |
| hot | 2 | 4 | 2 | 4 | 13 |
| like | 4 | 7 | 3 | 4 | 5 |
| like | 4 | 7 | 3 | 4 | 17 |
| like | 4 | 7 | 3 | 5 | 5 |
| like | 4 | 7 | 3 | 7 | 2 |
| nine | 3 | 6 | 2 | 3 | 0 |
| nine | 3 | 6 | 2 | 6 | 0 |
| old | 3 | 6 | 2 | 3 | 10 |
| old | 3 | 6 | 2 | 6 | 10 |
| pease | 1 | 2 | 2 | 1 | 0 |
| pease | 1 | 2 | 2 | 2 | 0 |
| pease | 1 | 2 | 2 | 2 | 19 |
| porridge | 1 | 2 | 2 | 1 | 6 |
| porridge | 1 | 2 | 2 | 2 | 6 |
| porridge | 1 | 2 | 2 | 2 | 19 |
| post | 1 | 1 | 1 | 1 | 22 |
| pot | 5 | 5 | 1 | 5 | 17 |
| some | 4 | 5 | 2 | 4 | 0 |
| some | 4 | 5 | 2 | 4 | 17 |
| some | 4 | 5 | 2 | 5 | 0 |
+----------+--------------+-------------+-----------+--------+----------+
27 rows in set (0.00 sec)
可以看到每個word對應一個DOC_ID和POSITION。此外,還記錄了FIRST_DOC_ID、LAST_DOC_ID、DOC_COUNT分別代表該word第一次出現文檔的ID,最后一次出現的文檔ID,以及該word在多少個文檔中存在。
若此時執行下面的SQL語句,會刪除FTS_DOC_ID為7的文檔
DELETE FROM fts_a WHERE FTS_DOC_ID=7;
InnoDB存儲引擎並不會直接刪除索引中對應的記錄,而是將刪除的文檔ID插入到DELETED表
SELECT * FROM information_schema.`INNODB_FT_DELETED`;
如果用戶想要徹底刪除倒排索引中該文檔的分詞信息,可以
mysql> SET GLOBAL innodb_optimize_fulltext_only=1; Query OK, 0 rows affected (0.00 sec) mysql> OPTIMIZE TABLE fts_a; +------------+----------+----------+----------+ | Table | Op | Msg_type | Msg_text | +------------+----------+----------+----------+ | iot2.fts_a | optimize | status | OK | +------------+----------+----------+----------+ 1 row in set (0.08 sec) mysql> SELECT * FROM information_schema.`INNODB_FT_DELETED`; +--------+ | DOC_ID | +--------+ | 7 | +--------+ 1 row in set (0.00 sec) mysql> SELECT * FROM information_schema.`INNODB_FT_BEING_DELETED`; +--------+ | DOC_ID | +--------+ | 7 | +--------+ 1 row in set (0.00 sec)
運行OPTIMIZE TABLE 可以將記錄徹底刪除,並且徹底刪除的文檔ID會記錄到INNODB_FT_BEGIN_DELETED中。此外,由於7這個文檔一倍刪除,因此不允許在插入這個文檔ID,否則會拋出異常
mysql> INSERT INTO fts_a SELECT 7,'I like this days'; ERROR 182 (HY000): Invalid InnoDB FTS Doc ID
stopword列表(stopword list)是本節最后闡述的一個概念,其表示該列表中的word不需要對其進行索引分詞操作。例如,對於the這個單詞,由於其不具有具體的意義,因此將其視為stopword,InnoDB存儲引擎有一張默認的stopword列表,在information_schema架構下,表名為INNODB_FT_DEFAULT_STOPWORD,默認為36個stopword可以通過參數innodb_ft_server_stopword_table來定義stopword列表,如
mysql> CREATE TABLE innodb_ft_bug ( -> value VARCHAR(18) NOT NULL DEFAULT '' -> ) ENGINE=INNODB DEFAULT CHARSET=utf8; #此處必須為utf8不然會碰到bug Query OK, 0 rows affected (0.07 sec) mysql> SET GLOBAL innodb_ft_server_stopword_table='iot2/innodb_ft_bug'; Query OK, 0 rows affected (0.00 sec)
遇到bug的情形
mysql> CREATE TABLE user_stopword(VALUE VARCHAR(30))ENGINE=INNODB; Query OK, 0 rows affected (0.03 sec) mysql> SET GLOBAL innodb_ft_server_stopword_table='iot2/user_stopword'; ERROR 1231 (42000): Variable 'innodb_ft_server_stopword_table' can't be set to the value of 'iot2/user_stopword'
觀察錯誤日志提示
InnoDB: invalid column name for stopword table iot2/user_stopword. Its first column must be named as 'value'.
使用全文檢索還有以下限制
每張表只能有一個全文檢索的索引
由多列組合而成的全文檢索的索引必須使用相同的字符集與排序規則
不支持沒有單詞界定符delimiter的語言,如中文 日文漢語等