MySQL全文檢索fulltext和中日韓文解析外掛ngram使用記錄
專案資料庫中遇到一個情況,有一個欄位儲存的是經銷廠商在工商局註冊時的(官方)全稱,但是使用者在查詢這個欄位時,很多時候都會使用到各種各樣的簡稱別稱。於是,對於該欄位,希望能夠實現類似於搜尋引擎的查詢。就像資料庫裡面存的是“中華人民共和國”,但希望是隻要使用者輸入“中國”、“中華”、“民國”、“中華民國”等關鍵字,最終都能定位到“中華人民共和國”的記錄。這種情況下模糊查詢和正則表示式有點捉襟見肘了,只能寄希望於其他特性。我們用的資料庫是MySQL,後來查了查文件,經過測試,藉助MySQL的全文檢索(Full-Text Search)和相應的中文解析外掛ngram解決這一問題。
在具體介紹MySQL的Full-Text Search和中文解析外掛ngram之前,先聊一聊關於自然語言的話題,MySQL在實現解析時將其分成兩大語系:第一種是use word delimiter language,這類語言單詞與單詞之間有天然的分隔符,例如英語;第二種是ideographic language,字詞之間沒有分隔符,其代表是漢語、日語和韓語(MySQL官方將其統稱為CJK:Chinese、Japanese、Korean)。因為個人只懂中文,學習過英語和日語,所以不能拿其他語言來作例證,這一部分關於自然語言劃分的內容只是看了MySQL的Reference Manual後受到啟發而自己設想的結論。
對於第一種語系,MySQL解析起來非常簡單,只要以空格(white space)作為界定符(delimiter)來進行分詞(tokenize)操作即可。這也是MySQL最初內建的全文檢索。MySQL5.6以前只能用於MyISAM表,5.6版本時InnoDB儲存引擎也實現並擴充套件了這種全文檢索。第二種CJK語系沒有分隔符,即MySQL原始無法自動分詞,所以需要一種新的實現來進行Full-Text Search,這就是前文提到的ngram外掛,由MySQL5.7.6開始引入。
Full-Text Search全文檢索是通過對欄位新增全文索引(FULLTEXT INDEX),然後將索引的每條記錄(document)的文字內容進行分詞,並將分好的單詞(word)記入輔助表(auxiliary table)來實現的。auxiliary table記錄了單詞與FULLTEXT index記錄行的對映。`INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE`系統表記錄了相關分詞資訊。設定引數“innodb_ft_aux_table”可以檢視該表中的記錄。
mysql> CREATE TABLE `player`.`articles` ( -> `FTS_DOC_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, -> `title` VARCHAR(200) NULL DEFAULT NULL, -> `body` TEXT NULL, -> PRIMARY KEY (`FTS_DOC_ID`), -> FULLTEXT INDEX `title` (`title`, `body`) -> ) -> ENGINE=InnoDB -> ; Query OK, 0 rows affected (0.25 sec) mysql> INSERT INTO `player`.`articles` (`title`, `body`) VALUES -> ('MySQL Tutorial','DBMS stands for DataBase ...'), -> ('How To Use MySQL Well','After you went through a ...'), -> ('Optimizing MySQL','In this tutorial we will show ...'), -> ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'), -> ('MySQL vs. YourSQL','In the following database comparison ...'), -> ('MySQL Security','When configured properly, MySQL ...'); Query OK, 6 rows affected (0.02 sec) Records: 6 Duplicates: 0 Warnings: 0 mysql> SET GLOBAL innodb_ft_aux_table="player/articles"; Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM `INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE` ORDER BY `doc_id`, `position` LIMIT 8; +----------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +----------+--------------+-------------+-----------+--------+----------+ | mysql | 1 | 6 | 6 | 1 | 0 | | tutorial | 1 | 3 | 2 | 1 | 6 | | dbms | 1 | 1 | 1 | 1 | 15 | | stands | 1 | 1 | 1 | 1 | 20 | | database | 1 | 5 | 2 | 1 | 31 | | use | 2 | 2 | 1 | 2 | 7 | | mysql | 1 | 6 | 6 | 2 | 11 | | well | 2 | 2 | 1 | 2 | 17 | +----------+--------------+-------------+-----------+--------+----------+ 8 rows in set (0.01 sec)
在以上DDL建表SQL中,有一個特殊的欄位“FTS_DOC_ID”。InnoDB儲存引擎固定使用該欄位與記錄行中的每個單詞(一對多)對映,型別必須為“BIGINT UNSIGNED NOT NULL”,並且為其主動加上一個UNIQUE INDEX。所以,InnoDB儲存引擎的Full-Text Search表一定會有一個“FTS_DOC_ID”欄位,如果在建表時沒有定義該欄位,MySQL會隱式地主動新增該欄位。因此,全文檢索表中的“FTS_DOC_ID”欄位直接對應`INNODB_FT_INDEX_CACHE`系統表中的“DOC_ID”列。
- WORD:分詞出來的單詞。
- FIRST_DOC_ID:該“WORD”首次出現的“DOC_ID”。
- LAST_DOC_ID:該“WORD”最後一次次出現的“DOC_ID”。
- DOC_COUNT:該“WORD”在多少行記錄(多少個“DOC_ID”)中存在。
- DOC_ID:對應全文檢索表中的“FTS_DOC_ID”欄位(顯式或隱式建立),對映該“WORD”屬於哪行記錄。
- POSITION。該“WORD”在該“DOC_ID”行的位置(位元組)。
上面的查詢為了簡約展示只返回了8條記錄,包含DOC_ID=1,即第一行記錄的“完整”分詞,而第二行記錄的資訊只顯示了部分。但是對比發現,第一行中的單詞“for”並沒有記入分詞,如果展示完整的查詢結果,會發現幾乎每行記錄都存在這種“棄詞”的情況。這裡涉及到stopword list,在tokenize時,stopword list中的word會直接忽略。在英語中,常見的像“a”、“an”和“the”等詞語並沒有實際的涵義,因此將其記入分詞並沒有多大的意義,只是增加了索引的大小而降低了查詢的效能。InnoDB儲存引擎預設的stopword記錄在`INFORMATION_SCHEMA`.`INNODB_FT_DEFAULT_STOPWORD`系統表,預設包含36個stopword。除此之外,MySQL系統引數“innodb_ft_min_token_size”、“innodb_ft_max_token_size”和“ft_min_word_len”、“ft_max_word_len”用來控制進行tokenize時word的長度區間,不在引數設定區間段內的word也無法全文檢索。前兩個引數控制InnoDB表,後兩個引數控制MyISAM表,它們都是靜態引數(需重啟MySQL伺服器來修改)。
Full-Text Search的查詢語句如下:MATCH()指定被查詢的欄位(全文索引列);AGAINST()指定查詢關鍵字表達式和查詢模式。
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
}
①NATURAL LANGUAGE MODE
查詢帶有指定word的文件。因“NATURAL LANGUAGE MODE”為預設全文檢索模式,所以查詢語句中可以省略該關鍵字。
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-------------------+------------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
+------------+-------------------+------------------------------------------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('database');
+------------+-------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-------------------+------------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
+------------+-------------------+------------------------------------------+
2 rows in set (0.00 sec)
MATCH()返回一個相關係數(relevance value),查詢結果會按relevance value值降序排列,即相關性最高的結果置於首位。relevance value根據以下條件計算。
- word是否在記錄中出現
- word在記錄中出現的次數
- word在索引欄位中的數量
- 多少行記錄包含該word
以下查詢可以檢視relevance value。
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
-> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('Tutorial' IN NATURAL LANGUAGE MODE) AS `score`
-> FROM `player`.`articles` AS `pa`
-> ORDER BY `score` DESC
-> LIMIT 4;
+------------+-----------------------+-------------------------------------+---------------------+
| FTS_DOC_ID | title | body | score |
+------------+-----------------------+-------------------------------------+---------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... | 0.22764469683170319 |
| 3 | Optimizing MySQL | In this tutorial we will show ... | 0.22764469683170319 |
| 2 | How To Use MySQL Well | After you went through a ... | 0 |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... | 0 |
+------------+-----------------------+-------------------------------------+---------------------+
4 rows in set (0.00 sec)
②BOOLEAN MODE
AGAINST()中關鍵字的前後字元會有特殊含義。BOOLEAN MODE全文檢索支援以下操作符。
- +
word必須存在。
- -
word必須不存在。
- (no operator)
該word可選,如果出現relevance value更高。
- @distance
僅用於InnoDB表。查詢多個單詞之間的距離是否在distance(位元組)內。
- > <
分別表示出現該word時增加和降低relevance value。
- ~
出現該word時relevance value變負值,用於製造噪音詞(“noise” word)。
- *
表示以該字串開頭的word。
- ''
''中的內容視作一個短語(整體)
mysql> /* 查詢包含“tutorial”但不包含“dbms”的記錄 */
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('+tutorial -dbms' IN BOOLEAN MODE);
+------------+------------------+-----------------------------------+
| FTS_DOC_ID | title | body |
+------------+------------------+-----------------------------------+
| 3 | Optimizing MySQL | In this tutorial we will show ... |
+------------+------------------+-----------------------------------+
1 row in set (0.01 sec)
mysql> /* 查詢“mysql”與“tutorial”之間的距離在2個位元組以內的記錄 */
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial" @2' IN BOOLEAN MODE);
+------------+----------------+------------------------------+
| FTS_DOC_ID | title | body |
+------------+----------------+------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
+------------+----------------+------------------------------+
1 row in set (0.01 sec)
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial" @4' IN BOOLEAN MODE);
+------------+------------------+-----------------------------------+
| FTS_DOC_ID | title | body |
+------------+------------------+-----------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 3 | Optimizing MySQL | In this tutorial we will show ... |
+------------+------------------+-----------------------------------+
2 rows in set (0.00 sec)
mysql> /* 根據單詞“tutorial”和“optimizing”統計relevance value:其中包含“optimizing”的記錄增加relevance value */
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
-> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial' IN BOOLEAN MODE) AS `score`
-> FROM `player`.`articles` AS `pa`
-> ORDER BY `score` DESC
-> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title | body | score |
+------------+------------------+-----------------------------------+---------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... | 0.22764469683170319 |
| 3 | Optimizing MySQL | In this tutorial we will show ... | 0.22764469683170319 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
-> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial >optimizing' IN BOOLEAN MODE) AS `score`
-> FROM `player`.`articles` AS `pa`
-> ORDER BY `score` DESC
-> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title | body | score |
+------------+------------------+-----------------------------------+---------------------+
| 3 | Optimizing MySQL | In this tutorial we will show ... | 1.8331639766693115 |
| 1 | MySQL Tutorial | DBMS stands for DataBase ... | 0.22764469683170319 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)
mysql> /* 查詢包含“tutorial”的記錄:其中包含“optimizing”的記錄relevance value置為負值 */
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
-> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial ~optimizing' IN BOOLEAN MODE) AS `score`
-> FROM `player`.`articles` AS `pa`
-> ORDER BY `score` DESC
-> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title | body | score |
+------------+------------------+-----------------------------------+---------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... | 0.22764469683170319 |
| 3 | Optimizing MySQL | In this tutorial we will show ... | -0.1668359637260437 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)
mysql> /* 查詢包含以字母“c”開頭的單詞的記錄 */
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('c*' IN BOOLEAN MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-------------------+------------------------------------------+
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
| 6 | MySQL Security | When configured properly, MySQL ... |
+------------+-------------------+------------------------------------------+
2 rows in set (0.00 sec)
mysql> /* 查詢包含“mysql”或者“tutorial”的記錄 */
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('mysql tutorial' IN BOOLEAN MODE);
+------------+-----------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+------------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 3 | Optimizing MySQL | In this tutorial we will show ... |
| 6 | MySQL Security | When configured properly, MySQL ... |
| 2 | How To Use MySQL Well | After you went through a ... |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... |
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
+------------+-----------------------+------------------------------------------+
6 rows in set (0.00 sec)
mysql> /* 查詢包含短語“mysql tutorial”的記錄 */
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial"' IN BOOLEAN MODE);
+------------+----------------+------------------------------+
| FTS_DOC_ID | title | body |
+------------+----------------+------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
+------------+----------------+------------------------------+
1 row in set (0.00 sec)
③(NATURAL LANGUAGE MODE)WITH QUERY EXPANSION
全文檢索擴充套件查詢。當查詢的關鍵詞太短,需要隱含資訊(implied knowledge)時比較有用。例如,當查詢“database”時可能意味著“MySQL”、“Oracle”、“DB2”以及“RDBMS”等詞語都應該匹配和返回。在查詢語句中新增WITH QUERY EXPANSION或者IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION啟用“blind query expansion”(也稱“automatic relevance feedback”)。擴充套件查詢分兩步執行。
- 根據原始的查詢關鍵字表達式進行全文檢索
- 根據第一步查詢的返回結果分詞後再進行一次全文檢索
因此,如果有記錄包含“database”和“MySQL”,第二步查詢也會找出那些包含“MySQL”的記錄,即便這些記錄沒有包含“database”。
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-------------------+------------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
+------------+-------------------+------------------------------------------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM `player`.`articles`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('database' WITH QUERY EXPANSION);
+------------+-----------------------+------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+------------------------------------------+
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 3 | Optimizing MySQL | In this tutorial we will show ... |
| 6 | MySQL Security | When configured properly, MySQL ... |
| 2 | How To Use MySQL Well | After you went through a ... |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... |
+------------+-----------------------+------------------------------------------+
6 rows in set (0.00 sec)
blind query expansion通常會返回不相關的記錄而極大地增加查詢結果的“噪音”(noise),因此僅當查詢語句表示式簡短的情況下使用。
在CJK語系中,為了解決word與word之間不使用分隔符而原始無法分詞的問題,MySQL引入ngram全文解析器(ngram full-text parser)。ngram的核心思想是使用者自定義分詞長度。一個ngram指的是給定文字序列中n個字元的連續序列。ngram解析器將文字序列分詞成n個字元的連續序列。例如,通過ngram full-text parser可以以不同的n值將“abcd”分詞。
- n=1: 'a', 'b', 'c', 'd'
- n=2: 'ab', 'bc', 'cd'
- n=3: 'abc', 'bcd'
- n=4: 'abcd'
ngram parser預設的分詞大小(token size)是2(bigram)。在此預設值下,字串“abc def”會被解析成四個分詞:“ab”、“bc”、“de”和“ef”。該值大小由引數“ngram_token_size”控制,值域為1 ~ 10。通常將其設定為你想要查詢的token的最大size。如果只想要搜尋單字元,將其設為1。越小的token size生成越小的full-text search索引和更快的查詢效能。如果需要搜尋由多於一個的字元組成的word,以此相應地設定“ngram_token_size”。例如,“Happy Birthday”在簡體中文裡是“生日快樂”,其中“生日”是“birthday”、“快樂”譯作“happy”。要搜尋類似的雙字元word,設定“ngram_token_size”的值為2或者更大。該引數為只讀變數,只能在啟動行或者配置檔案裡面設定。
ngram parser在解析時會消除空格。
- “ab cd”解析為:“ab”、“cd”
- “a bc”解析為:“bc”
在以空格作為分隔符來分詞的第一語系,stopword list是相同剔除(word與stopword相同時不會FULLTEXT索引);CJK語系則與此不同,stopword list為包含剔除。例如,假設ngram_token_size=2,一個包含“a,b”的記錄會被解析為“a,”和“,b”,如果逗號(“,”)被定義為stopword,那麼“a,”和“,b”都會被FULLTEXT索引排除,因為它們包含逗號。預設情況下,ngram parser使用MySQL預設的stopword list,它包含一列英文stopword。對於適用於CJK語系的stopword list,則必須自己建立(通過系統引數“innodb_ft_server_stopword_table”指定)。寬度超過“ngram_token_size”的stopword會被忽略。
要建立啟用ngram parser的FULLTEXT index,在建表的DDL中指定“WITH PARSER ngram
”。
mysql> CREATE TABLE `player`.`articles_ngram` (
-> `FTS_DOC_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-> `title` VARCHAR(200) NULL DEFAULT NULL,
-> `body` TEXT NULL,
-> PRIMARY KEY (`FTS_DOC_ID`),
-> FULLTEXT INDEX `title` (`title`, `body`) WITH PARSER ngram
-> )
-> ENGINE=InnoDB CHARACTER SET utf8mb4
-> ;
Query OK, 0 rows affected (0.11 sec)
mysql> SET NAMES utf8mb4;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO `player`.`articles_ngram` (`title`, `body`) VALUES
-> ('中華人民共和國', '成立於1949年10月1日'),
-> ('中華', '這裡是指中華民族'),
-> ('中國', '擁有五千年曆史的國家'),
-> ('中華民國', '1912年~1949年的中國大陸;1949年至今的中華臺灣'),
-> ('民國', '應該是指民國時期吧(1912年~1949年的中國)'),
-> ('日本國', '太陽が昇るお國'),
-> ('大和', '日本國家的主要民族'),
-> ('島國', '就是日本 國家的元首叫天皇'),
-> ('UK', 'country of the sun say goodbye'),
-> ('英國', '老牌資本主義國家');
Query OK, 10 rows affected (0.01 sec)
Records: 10 Duplicates: 0 Warnings: 0
mysql> SET GLOBAL innodb_ft_aux_table="player/articles_ngram";
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW GLOBAL VARIABLES LIKE 'ngram_token_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| ngram_token_size | 2 |
+------------------+-------+
1 row in set (0.02 sec)
mysql> SELECT * FROM `INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE` AS `ii` WHERE `ii`.`DOC_ID` = 1 ORDER BY `ii`.`POSITION`;
+--------+--------------+-------------+-----------+--------+----------+
| WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+--------+--------------+-------------+-----------+--------+----------+
| 中華 | 1 | 4 | 3 | 1 | 0 |
| 華人 | 1 | 1 | 1 | 1 | 3 |
| 人民 | 1 | 1 | 1 | 1 | 6 |
| 民共 | 1 | 1 | 1 | 1 | 9 |
| 共和 | 1 | 1 | 1 | 1 | 12 |
| 和國 | 1 | 1 | 1 | 1 | 15 |
| 成立 | 1 | 1 | 1 | 1 | 22 |
| 立於 | 1 | 1 | 1 | 1 | 25 |
| 於1 | 1 | 1 | 1 | 1 | 28 |
| 19 | 1 | 5 | 3 | 1 | 31 |
| 94 | 1 | 5 | 3 | 1 | 32 |
| 49 | 1 | 5 | 3 | 1 | 33 |
| 9年 | 1 | 5 | 3 | 1 | 34 |
| 年1 | 1 | 1 | 1 | 1 | 35 |
| 10 | 1 | 1 | 1 | 1 | 38 |
| 0月 | 1 | 1 | 1 | 1 | 39 |
| 月1 | 1 | 1 | 1 | 1 | 40 |
| 1日 | 1 | 1 | 1 | 1 | 43 |
+--------+--------------+-------------+-----------+--------+----------+
18 rows in set (0.01 sec)
全文檢索的SQL查詢語法對於ngram解析器外掛同樣適用,但解析方式存在一些不同之處。
①ngram Parser Term Search
- 在NATURAL LANGUAGE MODE檢索模式下,查詢關鍵字表達式被轉換為若干個ngram詞語的聯合。例如,字串“abc”(假設ngram_token_size=2)會被轉換為“ab bc”。給出兩行記錄,一行包含“ab”而另一行包含“abc”,這兩行記錄都匹配檢索詞語“ab bc”。即匹配其一即可返回。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('中華人民' IN NATURAL LANGUAGE MODE);
+------------+-----------------------+---------------------------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+---------------------------------------------------------------+
| 1 | 中華人民共和國 | 成立於1949年10月1日 |
| 2 | 中華 | 這裡是指中華民族 |
| 4 | 中華民國 | 1912年~1949年的中國大陸;1949年至今的中華臺灣 |
+------------+-----------------------+---------------------------------------------------------------+
3 rows in set (0.00 sec)
- 在BOOLEAN MODE檢索模式下,查詢關鍵字表達式被轉換為一個ngram短語檢索。例如,字串“abc”(假設ngram_token_size=2)會被轉換為'"ab bc"'。給出兩行記錄,一行包含“ab”而另一行包含“abc”,只有包含“abc”的記錄行匹配檢索短語'"ab bc"'。即完全匹配才可返回。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('中華人民' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+---------------------------+
| 1 | 中華人民共和國 | 成立於1949年10月1日 |
+------------+-----------------------+---------------------------+
1 row in set (0.00 sec)
②ngram Parser Wildcard Search
由於ngram FULLTEXT index僅包含ngram分詞,而不包含詞語的詞首資訊,所以萬用字元檢索可能返回預想之外的結果。
- 如果萬用字元檢索的字首詞語小於“ngram_token_size”,查詢會返回包含以該字首詞語開頭的ngram分詞的所有索引行。例如,假設ngram_token_size=2,對於“a*”的檢索會返回所有以“a”開頭的記錄。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('中*' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+---------------------------------------------------------------+
| 2 | 中華 | 這裡是指中華民族 |
| 4 | 中華民國 | 1912年~1949年的中國大陸;1949年至今的中華臺灣 |
| 1 | 中華人民共和國 | 成立於1949年10月1日 |
| 3 | 中國 | 擁有五千年曆史的國家 |
| 5 | 民國 | 應該是指民國時期吧(1912年~1949年的中國) |
+------------+-----------------------+---------------------------------------------------------------+
5 rows in set (0.01 sec)
- 如果萬用字元檢索的字首詞語大於“ngram_token_size”,該字首詞語會被轉換為一個ngram短語,與此同時,萬用字元符號會被忽略。例如,假設ngram_token_size=2,“abc*”萬用字元檢索會被轉換為“ab bc”(“ngram Parser Term Search”的BOOLEAN MODE)。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('中華人民*' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------+
| FTS_DOC_ID | title | body |
+------------+-----------------------+---------------------------+
| 1 | 中華人民共和國 | 成立於1949年10月1日 |
+------------+-----------------------+---------------------------+
1 row in set (0.00 sec)
③ngram Parser Phrase Search
短語檢索會被轉換為ngram短語檢索。例如,檢索短語“abc”會被轉換為“ab bc”,包含“abc”和“ab bc”的記錄都會被返回。檢索短語“abc def”會被轉換為“ab bc de ef”,包含“abcdef”的記錄不會被返回。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('"日本國家"' IN BOOLEAN MODE);
+------------+--------+-----------------------------+
| FTS_DOC_ID | title | body |
+------------+--------+-----------------------------+
| 7 | 大和 | 日本國家的主要民族 |
+------------+--------+-----------------------------+
1 row in set (0.01 sec)
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('"日本 國家"' IN BOOLEAN MODE);
+------------+--------+---------------------------------------+
| FTS_DOC_ID | title | body |
+------------+--------+---------------------------------------+
| 8 | 島國 | 就是日本 國家的元首叫天皇 |
+------------+--------+---------------------------------------+
1 row in set (0.00 sec)
query expansion擴充套件查詢同樣適用於ngram parser。
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('國家');
+------------+--------+---------------------------------------+
| FTS_DOC_ID | title | body |
+------------+--------+---------------------------------------+
| 3 | 中國 | 擁有五千年曆史的國家 |
| 7 | 大和 | 日本國家的主要民族 |
| 8 | 島國 | 就是日本 國家的元首叫天皇 |
| 10 | 英國 | 老牌資本主義國家 |
+------------+--------+---------------------------------------+
4 rows in set (0.00 sec)
mysql> SELECT * FROM `player`.`articles_ngram`
-> WHERE MATCH (`title`, `body`)
-> AGAINST ('國家' WITH QUERY EXPANSION);
+------------+--------------+---------------------------------------------------------------+
| FTS_DOC_ID | title | body |
+------------+--------------+---------------------------------------------------------------+
| 8 | 島國 | 就是日本 國家的元首叫天皇 |
| 3 | 中國 | 擁有五千年曆史的國家 |
| 10 | 英國 | 老牌資本主義國家 |
| 7 | 大和 | 日本國家的主要民族 |
| 6 | 日本國 | 太陽が昇るお國 |
| 2 | 中華 | 這裡是指中華民族 |
| 4 | 中華民國 | 1912年~1949年的中國大陸;1949年至今的中華臺灣 |
| 5 | 民國 | 應該是指民國時期吧(1912年~1949年的中國) |
+------------+--------------+---------------------------------------------------------------+
8 rows in set (0.00 sec)
除了上文第一語系解析時提到的用於InnoDB的“innodb_ft_min_token_size”和“innodb_ft_max_token_size”以及用於MyISAM的“ft_min_word_len”、“ft_max_word_len”(ngram parser會自動忽略其值設定),其他內建的Full-Text Search配置引數同樣適用於ngram parser。
MySQL同時提供了一個專用於日語的MeCab full-text parser,將記錄行tokenize為有實際意義的詞語。
參考: