MySQL - ngram 全文解析器



通常在全文搜索中,内置的 MySQL 全文解析器将单词之间的空格视为分隔符。这决定了单词的实际开始和结束位置,使搜索更简单。但是,这仅适用于使用空格分隔单词的语言。

几种表意文字语言,如中文、日语和韩语,不使用单词分隔符。为了支持这些语言的全文搜索,使用了 ngram 解析器。此解析器由 InnoDB 和 MyISAM 存储引擎都支持。

ngram 全文解析器

ngram 是从给定文本序列中连续的“n”个字符。ngram 解析器将文本序列划分为标记,作为 n 个字符的连续序列。

例如,考虑文本“Tutorial”并观察 ngram 解析器如何对其进行标记化:

n=1: 'T', 'u', 't', 'o', 'r', 'i', 'a', 'l'
n=2: 'Tu', 'ut', 'to' 'or', 'ri', 'ia' 'al'
n=3: 'Tut', 'uto', 'tor', 'ori', 'ria', 'ial'
n=4: 'Tuto', 'utor', 'tori', 'oria', 'rial'
n=5: 'Tutor', 'utori', 'toria', 'orial'
n=6: 'Tutori', 'utoria', 'torial'
n=7: 'Tutoria', 'utorial'
n=8: 'Tutorial'

ngram 全文解析器是一个内置的服务器插件。与其他内置服务器插件一样,它在服务器启动时自动加载。

配置 ngram 令牌大小

要更改标记大小(从其默认大小 2 开始),请使用 ngram_token_size 配置选项。ngram 值的范围是 1 到 10。但为了提高搜索查询的速度,请使用较小的标记大小;因为较小的标记大小允许使用较小的全文搜索索引进行更快的搜索。

因为 ngram_token_size 是一个只读变量,所以您只能使用两种选项来设置它的值

在启动字符串中设置 --ngram_token_size

mysqld --ngram_token_size=1

在配置文件“my.cnf”中设置 ngram_token_size

[mysqld]

ngram_token_size=1

Learn MySQL in-depth with real-world projects through our MySQL certification course. Enroll and become a certified expert to boost your career.

使用 ngram 解析器创建 FULLTEXT 索引

可以使用 FULLTEXT 关键字在表的列上创建 FULLTEXT 索引。这与 CREATE TABLE、ALTER TABLE 或 CREATE INDEX SQL 语句一起使用;您只需指定“WITH PARSER ngram”。以下是语法:

CREATE TABLE table_name ( column_name1 datatype, column_name2 datatype, column_name3 datatype, ... FULLTEXT (column_name(s)) WITH PARSER NGRAM ) ENGINE=INNODB CHARACTER SET UTF8mb4;

示例

在此示例中,我们使用 CREATE TABLE 语句创建 FULLTEXT 索引,如下所示:

CREATE TABLE blog ( ID INT AUTO_INCREMENT NOT NULL, TITLE VARCHAR(255), DESCRIPTION TEXT, FULLTEXT ( TITLE, DESCRIPTION ) WITH PARSER NGRAM, PRIMARY KEY(id) ) ENGINE=INNODB CHARACTER SET UTF8MB4; SET NAMES UTF8MB4;

现在,将数据(以任何表意文字语言)插入到创建的此表中:

INSERT INTO BLOG VALUES (NULL, '教程', '教程是对一个概念的冗长研究'), (NULL, '文章', '文章是关于一个概念的基于事实的小信息');

要检查文本是如何标记化的,请执行以下语句:

SET GLOBAL innodb_ft_aux_table = "customers/blog"; SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position;

ngram 解析器空格处理

ngram 解析器在解析时会消除任何空格字符。例如,考虑以下带有标记大小 2 的 TEXT:

  • “ab cd”解析为“ab”、“cd”

  • “a bc”解析为“bc”

ngram 解析器停用词处理

除了空格字符之外,MySQL 还具有一个停用词列表,其中包含被视为停用词的各种词。如果解析器在文本中遇到停用词列表中存在的任何单词,则该单词将从索引中排除。

普通短语搜索将转换为 ngram 短语搜索。例如,搜索短语“abc”将转换为“ab bc”,这将返回包含“abc”和“ab bc”的文档;搜索短语“abc def”将转换为“ab bc de ef”,这将返回包含“abc def”和“ab bc de ef”的文档。包含“abcdef”的文档不会返回。

对于自然语言模式搜索,搜索词将转换为 ngram 术语的并集。例如,字符串“abc”(假设 ngram_token_size=2)将转换为“ab bc”。给定两个文档,一个包含“ab”,另一个包含“abc”,搜索词“ab bc”匹配这两个文档。

对于布尔模式搜索,搜索词将转换为 ngram 短语搜索。例如,字符串“abc”(假设 ngram_token_size=2)将转换为““ab bc””。给定两个文档,一个包含“ab”,另一个包含“abc”,搜索短语““ab bc””仅匹配包含“abc”的文档。

因为 ngram FULLTEXT 索引仅包含 ngram,并且不包含有关术语开头的信息,所以通配符搜索可能会返回意外的结果。以下行为适用于使用 ngram FULLTEXT 搜索索引的通配符搜索

  • 如果通配符搜索的前缀词短于 ngram 令牌大小,则查询返回所有包含以该前缀词开头的 ngram 令牌的索引行。例如,假设 ngram_token_size=2,则对 "a*" 进行搜索将返回所有以 "a" 开头的行。

  • 如果通配符搜索的前缀词长于 ngram 令牌大小,则将前缀词转换为 ngram 短语,并忽略通配符运算符。例如,假设 ngram_token_size=2,则 "abc*" 通配符搜索将转换为 "ab bc"。

使用客户端程序的 ngram 全文解析器

我们也可以使用客户端程序执行 ngram 全文解析器操作。

语法

要通过 PHP 程序执行 ngram 全文解析器,我们需要使用 **mysqli** 函数 **query()** 执行 "Create" 语句,如下所示:

$sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), DESCRIPTION TEXT, FULLTEXT ( title, DESCRIPTION ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4"; $mysqli->query($sql);

要通过 JavaScript 程序执行 ngram 全文解析器,我们需要使用 **mysql2** 库的 **query()** 函数执行 "Create" 语句,如下所示:

sql = `CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), DESCRIPTION TEXT, FULLTEXT ( title, DESCRIPTION ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4`; con.query(sql);

要通过 Java 程序执行 ngram 全文解析器,我们需要使用 **JDBC** 函数 **execute()** 执行 "Create" 语句,如下所示:

String sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT," + " FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4"; statement.execute(sql);

要通过 Python 程序执行 ngram 全文解析器,我们需要使用 **MySQL Connector/Python** 的 **execute()** 函数执行 "Create" 语句,如下所示:

create_table_query = 'CREATE TABLE blog(ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT (title, description) WITH PARSER NGRAM, PRIMARY KEY(id)) ENGINE=INNODB CHARACTER SET UTF8MB4' cursorObj.execute(queryexpansionfulltext_search)

示例

以下是程序:

$dbhost = "localhost"; $dbuser = "root"; $dbpass = "password"; $dbname = "TUTORIALS"; $mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname); if ($mysqli->connect_errno) { printf("Connect failed: %s ", $mysqli->connect_error); exit(); } // printf('Connected successfully.'); /*CREATE Table*/ $sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4"; $result = $mysqli->query($sql); if ($result) { printf("Table created successfully...!\n"); } //insert data $q = "INSERT INTO blog (id, title, description) VALUES (NULL, '教程', '教程是对一个概念的冗长研究'), (NULL, '文章', '文章是关于一个概念的基于事实的小信息')"; if ($res = $mysqli->query($q)) { printf("Data inserted successfully...!\n"); } //we will use the below statement to see how the ngram tokenizes the data: $setglobal = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'"; if ($mysqli->query($setglobal)) { echo "global innodb_ft_aux_table set...!"; } $s = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position "; if ($r = $mysqli->query($s)) { print_r($r); } //display data (ngram parser phrase search); $query = "SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')"; if ($r = $mysqli->query($query)) { printf("Table Records: \n"); while ($row = $r->fetch_assoc()) { printf( "ID: %d, Title: %s, Descriptions: %s", $row["id"], $row["title"], $row["description"] ); printf("\n"); } } else { printf("Failed"); } $mysqli->close();

输出

获得的输出如下所示:

global innodb_ft_aux_table set...!mysqli_result Object
(
    [current_field] => 0
    [field_count] => 6
    [lengths] =>
    [num_rows] => 62
    [type] => 0
)
Table Records:
ID: 1, Title: 教程, Descriptions: 教程是对一个概念的冗长研究
ID: 3, Title: 教程, Descriptions: 教程是对一个概念的冗长研究
var mysql = require("mysql2"); var con = mysql.createConnection({ host: "localhost", user: "root", password: "password", }); //Connecting to MySQL con.connect(function (err) { if (err) throw err; // console.log("Connected successfully...!"); // console.log("--------------------------"); sql = "USE TUTORIALS"; con.query(sql); //create a table... sql = `CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4`; con.query(sql); //insert data sql = `INSERT INTO blog (id, title, description) VALUES (NULL, '教程', '教程是对一个概念的冗长研究'), (NULL, '文章', '文章是关于一个概念的基于事实的小信息')`; con.query(sql); //we will use the below statement to see how the ngram tokenizes the data: sql = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'"; con.query(sql); //display the table details; sql = `SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')`; con.query(sql, function (err, result) { if (err) throw err; console.log(result); }); });

输出

获得的输出如下所示:

[ { id: 1, title: '教程', description: '教程是对一个概念的冗长研究' } ]
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class NgRamFSearch { public static void main(String[] args) { String url = "jdbc:mysql://127.0.0.1:3306/TUTORIALS"; String username = "root"; String password = "password"; try { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, username, password); Statement statement = connection.createStatement(); System.out.println("Connected successfully...!"); //creating a table that takes fulltext column with parser ngram...! String sql = "CREATE TABLE blog (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT," + " FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4"; statement.execute(sql); //System.out.println("Table created successfully...!"); //inserting data to the table String insert = "INSERT INTO blog (id, title, description) VALUES (NULL, '教程', '教程是对一个概念的冗长研究')," + " (NULL, '文章', '文章是关于一个概念的基于事实的小信息')"; statement.execute(insert); //System.out.println("Data inserted successfully...!"); //we will use the below statement to see how the ngram tokenizes the data: String set_global = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'"; statement.execute(set_global); ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position "); System.out.println("Information schema order by Id...!"); while (resultSet.next()){ System.out.println(resultSet.getString(1)+" "+resultSet.getString(2)); } //displaying the data...! String query = "SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')"; ResultSet resultSet1 = statement.executeQuery(query); System.out.println("table records:"); while (resultSet1.next()){ System.out.println(resultSet1.getString(1)+" "+resultSet1.getString(2)+ " "+resultSet1.getString(3)); } connection.close(); } catch (Exception e) { System.out.println(e); } } }

输出

获得的输出如下所示:

Connected successfully...!
Information schema order by Id...!
教程 2
教程 2
程是 2
是对 2
对一 2
一个 2
个概 2
概念 2
念的 2
的冗 2
冗长 2
长研 2
研究 2
文章 3
文章 3
章是 3
是关 3
关于 3
于一 3
一个 2
个概 2
概念 2
念的 2
的基 3
基于 3
于事 3
事实 3
实的 3
的小 3
小信 3
信息 3
table records:
1 教程 教程是对一个概念的冗长研究
import mysql.connector # Establishing the connection connection = mysql.connector.connect( host='localhost', user='root', password='password', database='tut' ) # Creating a cursor object cursorObj = connection.cursor() # Create the blog table with NGRAM full-text parser create_table_query = ''' CREATE TABLE blog ( id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT (title, description) WITH PARSER NGRAM, PRIMARY KEY(id) ) ENGINE=INNODB CHARACTER SET UTF8MB4; ''' cursorObj.execute(create_table_query) print("Table 'blog' is created successfully!") # Set the character set to UTF8MB4 set_charset_query = "SET NAMES UTF8MB4;" cursorObj.execute(set_charset_query) print("Character set is set to UTF8MB4.") # Insert data into the blog table data_to_insert = [ ('教程', '教程是对一个概念的冗长研究'), ('文章', '文章是关于一个概念的基于事实的小信息') ] insert_query = "INSERT INTO blog (title, description) VALUES (%s, %s)" cursorObj.executemany(insert_query, data_to_insert) connection.commit() print("Data inserted into the 'blog' table.") # Query the INNODB_FT_INDEX_CACHE table to get the full-text index information query_index_cache_query = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position;" cursorObj.execute(query_index_cache_query) results = cursorObj.fetchall() print("Results of INNODB_FT_INDEX_CACHE table:") for row in results: print(row) # Close the cursor and connection cursorObj.close() connection.close()

输出

获得的输出如下所示:

Table 'blog' is created successfully!
Character set is set to UTF8MB4.
Data inserted into the 'blog' table.
Results of INNODB_FT_INDEX_CACHE table:
广告