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

使用 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://: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:
广告

© . All rights reserved.