### 12.10.1 自然语言全文搜索

默认情况下或使用`在自然语言模式下`修饰符[`匹配（）`](fulltext-search.html#function_match)函数针对文本集合执行自然语言搜索字符串。集合是包含在一个集合中的一个或多个列的集合`全文`指数。搜索字符串作为参数给出`反对（）`.对于表中的每一行，[`匹配（）`](fulltext-search.html#function_match)返回相关值；也就是说，搜索字符串与该行中的文本之间的相似性度量[`匹配（）`](fulltext-search.html#function_match)列表。

```
mysql> CREATE TABLE articles (
          id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
          title VARCHAR(200),
          body TEXT,
          FULLTEXT (title,body)
        ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.08 sec)

mysql> 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 tutorial, we 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.01 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM articles
        WHERE MATCH (title,body)
        AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----+-------------------+------------------------------------------+
| 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)
```

默认情况下，搜索以不区分大小写的方式执行。要执行区分大小写的全文搜索，请对索引列使用区分大小写或二进制排序规则。例如，使用`utf8mb4`可以为字符集分配一个排序规则`utf8mb4_0900_as_cs`要么`utf8mb4_bin`使其对全文搜索区分大小写。

什么时候[`匹配（）`](fulltext-search.html#function_match)用于一个`在哪里`子句，如前面所示的示例，只要满足以下条件，返回的行将自动以最高相关性优先排序：

-   必须没有明确的`订购方式`条款。

-   必须使用全文索引扫描而不是表扫描来执行搜索。

-   如果查询连接表，全文索引扫描必须是连接中最左边的非常量表。

    鉴于刚刚列出的条件，通常使用`订购方式`必要或需要时的明确排序顺序。

    相关性值是非负浮点数。零相关性意味着没有相似性。相关性是根据行（文档）中的单词数、行中唯一单词的数量、集合中的单词总数以及包含特定单词的行数来计算的。

笔记

术语“文档”可以与术语“行”互换使用，两个术语都指行的索引部分。术语“集合”是指索引列并包含所有行。

要简单地计算匹配项，您可以使用如下查询：

```
mysql> SELECT COUNT(*) FROM articles
    WHERE MATCH (title,body)
    AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)
```

您可能会发现按如下方式重写查询会更快：

```
mysql> SELECT
    COUNT(IF(MATCH (title,body) AGAINST ('database' IN NATURAL LANGUAGE MODE), 1, NULL))
    AS count
    FROM articles;
+-------+
| count |
+-------+
|     2 |
+-------+
1 row in set (0.03 sec)
```

第一个查询做了一些额外的工作（按相关性对结果进行排序），但也可以使用基于`在哪里`条款。如果搜索匹配几行，则索引查找可能会使第一个查询更快。第二个查询执行全表扫描，如果搜索词出现在大多数行中，这可能比索引查找更快。

对于自然语言全文搜索，在[`匹配（）`](fulltext-search.html#function_match)函数必须是包含在某些列中的相同列`全文`表中的索引。对于前面的查询，请注意在[`匹配（）`](fulltext-search.html#function_match)功能 （`标题`和`身体`) 与定义中的名称相同`文章`表的`全文`指数。要搜索`标题`要么`身体`分开，你会创建单独的`全文`每列的索引。

您还可以执行布尔搜索或带有查询扩展的搜索。这些搜索类型在[第 12.10.2 节，“布尔全文搜索”](fulltext-boolean.html)， 和[第 12.10.3 节，“带有查询扩展的全文搜索”](fulltext-query-expansion.html).

使用索引的全文搜索只能命名单个表中的列[`匹配（）`](fulltext-search.html#function_match)子句，因为一个索引不能跨越多个表。为了`MyISAM`表，可以在没有索引的情况下进行布尔搜索（尽管速度较慢），在这种情况下，可以从多个表中命名列。

前面的例子是一个基本的例子，展示了如何使用[`匹配（）`](fulltext-search.html#function_match)按相关性递减的顺序返回行的函数。下一个示例显示如何显式检索相关值。返回的行没有排序，因为[`选择`](select.html)声明既不包括`在哪里`也不`订购方式`条款：

```
mysql> SELECT id, MATCH (title,body)
    AGAINST ('Tutorial' IN NATURAL LANGUAGE MODE) AS score
    FROM articles;
+----+---------------------+
| id | score               |
+----+---------------------+
|  1 | 0.22764469683170319 |
|  2 |                   0 |
|  3 | 0.22764469683170319 |
|  4 |                   0 |
|  5 |                   0 |
|  6 |                   0 |
+----+---------------------+
6 rows in set (0.00 sec)
```

下面的例子更复杂。该查询返回相关性值，它还按相关性递减的顺序对行进行排序。为了达到这个结果，指定[`匹配（）`](fulltext-search.html#function_match)两次：一次在[`选择`](select.html)列表和一次在`在哪里`条款。这不会导致额外的开销，因为 MySQL 优化器注意到这两个[`匹配（）`](fulltext-search.html#function_match)调用是相同的，并且只调用一次全文搜索代码。

```
mysql> SELECT id, body, MATCH (title,body) AGAINST
    ('Security implications of running MySQL as root'
    IN NATURAL LANGUAGE MODE) AS score
    FROM articles WHERE MATCH (title,body) AGAINST
    ('Security implications of running MySQL as root'
    IN NATURAL LANGUAGE MODE);
+----+-------------------------------------+-----------------+
| id | body                                | score           |
+----+-------------------------------------+-----------------+
|  4 | 1. Never run mysqld as root. 2. ... | 1.5219271183014 |
|  6 | When configured properly, MySQL ... | 1.3114095926285 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)
```

用双引号 (`"`) 字符仅匹配包含该短语的行*从字面上看，因为它是键入的*.全文引擎将短语拆分为单词并在`全文`词的索引。非单词字符不需要完全匹配：短语搜索只需要匹配包含与短语完全相同的单词并且顺序相同。例如，`“测试用语”`火柴`“测试，短语”`.如果短语不包含索引中的单词，则结果为空。例如，如果所有单词都是停用词或短于索引词的最小长度，则结果为空。

MySQL的`全文`实现将任何真正的单词字符（字母、数字和下划线）序列视为一个单词。该序列也可能包含撇号 (`'`)，但连续不超过一个。这意味着`啊啊啊啊`被视为一个词，但`aaa''bbb`被视为两个词。单词开头或结尾的撇号被`全文`解析器；`'aaa'bbb'`将被解析为`啊啊啊啊`.

内置的`全文`解析器通过查找某些分隔符来确定单词的开始和结束位置；例如，``（空间），`,`（逗号），和`.`（时期）。如果单词没有用分隔符分隔（例如在中文中），则内置`全文`解析器无法确定单词的开始或结束位置。能够将此类语言中的单词或其他索引术语添加到`全文`使用内置索引`全文`解析器，您必须对它们进行预处理，以便它们由一些任意分隔符分隔。或者，您可以创建`全文`使用 ngram 解析器插件（适用于中文、日语或韩语）或 MeCab 解析器插件（适用于日语）进行索引。

可以编写一个插件来替换内置的全文解析器。有关详细信息，请参阅[MySQL 插件 API](https://dev.mysql.com/doc/extending-mysql/8.0/en/plugin-api.html).例如解析器插件源代码，请参见`插件/全文`MySQL 源代码分发的目录。

在全文搜索中会忽略某些词：

-   任何太短的单词都会被忽略。全文搜索找到的默认最小单词长度为三个字符`InnoDB`搜索索引，或四个字符`MyISAM`.您可以通过在创建索引之前设置配置选项来控制截止：[`innodb_ft_min_token_size`](innodb-parameters.html#sysvar_innodb_ft_min_token_size)配置选项`InnoDB`搜索索引，或[`ft_min_word_len`](server-system-variables.html#sysvar_ft_min_word_len)为了`MyISAM`.

    笔记

    此行为不适用于`全文`使用 ngram 解析器的索引。对于 ngram 解析器，令牌长度由[`ngram_token_size`](server-system-variables.html#sysvar_ngram_token_size)选项。

-   停用词列表中的单词将被忽略。停用词是诸如“the”或“some”之类的词，它非常常见，以至于被认为具有零语义值。有一个内置的停用词列表，但它可以被用户定义的列表覆盖。停用词列表和相关的配置选项是不同的`InnoDB`搜索索引和`MyISAM`那些。停用词处理由配置选项控制[`innodb_ft_enable_stopword`](innodb-parameters.html#sysvar_innodb_ft_enable_stopword),[`innodb_ft_server_stopword_table`](innodb-parameters.html#sysvar_innodb_ft_server_stopword_table)， 和[`innodb_ft_user_stopword_table`](innodb-parameters.html#sysvar_innodb_ft_user_stopword_table)为了`InnoDB`搜索索引，以及[`ft_stopword_file`](server-system-variables.html#sysvar_ft_stopword_file)为了`MyISAM`那些。

    看[第 12.10.4 节，“全文停用词”](fulltext-stopwords.html)查看默认停用词列表以及如何更改它们。默认的最小字长可以按照中所述进行更改[第 12.10.6 节，“微调 MySQL 全文搜索”](fulltext-fine-tuning.html).

    集合和查询中的每个正确单词都根据其在集合或查询中的重要性进行加权。因此，出现在许多文档中的单词具有较低的权重，因为它在该特定集合中具有较低的语义值。相反，如果单词很少见，它会获得更高的权重。组合单词的权重以计算行的相关性。这种技术最适用于大型集合。

MyISAM 限制

对于非常小的表，词分布并不能充分反映它们的语义价值，这种模型有时可能会在搜索索引上产生奇怪的结果`MyISAM`表。例如，虽然“MySQL”这个词出现在每行`文章`前面显示的表，在 a 中搜索单词`MyISAM`搜索索引不产生任何结果：

```
mysql> SELECT * FROM articles
    WHERE MATCH (title,body)
    AGAINST ('MySQL' IN NATURAL LANGUAGE MODE);
Empty set (0.00 sec)
```

搜索结果为空，因为“MySQL”一词出现在至少 50% 的行中，因此被有效地视为停用词。这种过滤技术更适合大型数据集，您可能不希望结果集从 1GB 表中每隔一行返回一次，而不适用于可能导致流行术语结果不佳的小型数据集。

当您第一次尝试全文搜索以了解其工作原理时，50% 的阈值会让您感到惊讶，并`InnoDB`表更适合全文搜索的实验。如果你创建一个`MyISAM`表中只插入一两行文本，文本中的每个单词至少出现在 50% 的行中。因此，在表包含更多行之前，任何搜索都不会返回任何结果。需要绕过 50% 限制的用户可以在`InnoDB`表，或使用中解释的布尔搜索模式[第 12.10.2 节，“布尔全文搜索”](fulltext-boolean.html).
