全文搜索 Innodb 失败,MyIsam Returns 结果

FullText Search Innodb Fails, MyIsam Returns Results

我已将 table 从 myisam 升级到 innodb,但性能不一样。 innodb returns 一个 0 得分时应该有某种关系。 myisam table returns 匹配相同的术语(我保留了旧 table 的副本,所以我仍然可以 运行 相同的查询)。

SELECT MATCH (COLUMNS) AGAINST ('+"Term Ex"' IN BOOLEAN MODE) as score
FROM table_myisam
where id = 1;

Returns:

+-------+
| score |
+-------+
|     1 |
+-------+

但是:

SELECT MATCH (COLUMNS) AGAINST ('+"Term Ex"' IN BOOLEAN MODE) as score
FROM table
where id = 1;

returns:

+-------+
| score |
+-------+
|     0 |
+-------+

我认为 ex 可能没有被索引,因为 innodb_ft_min_token_size 被设置为 3。我将其降低到 1 并优化了 table 但这没有任何影响。列内容有 99 个字符长,因此我推测由于 innodb_ft_max_token_size 而未对整个列进行索引。我也将其增加到 150 和 运行 再次优化但再次得到相同的结果。

这些 table 之间的唯一区别是引擎和字符集。 table 正在使用 utf8myisam table 正在使用 latin1.

有没有人见过这些行为,或者有解决方法的建议?

更新: 我将 ft_stopword_file="" 添加到我的 my.cnf 和 运行 OPTIMIZE TABLE table 中。这次我得到了

optimize | note | Table does not support optimize, doing recreate + analyze instead

此更改后查询有效。 Ex 不是停用词,所以不确定为什么会有所不同。

失败的新查询是:

SELECT MATCH (Columns) AGAINST ('+Term +Ex +in' IN BOOLEAN MODE) as score FROM Table where id = 1;

+-------+
| score |
+-------+
|     0 |
+-------+

in 导致失败,但这是我 table 中的下一个词。

SELECT MATCH (Columns) AGAINST ('+Term +Ex' IN BOOLEAN MODE) as score FROM Table where id = 1;

+--------------------+
| score              |
+--------------------+
| 219.30206298828125 |
+--------------------+

我也试过 CREATE TABLE my_stopwords(value VARCHAR(30)) ENGINE = INNODB;,然后用 innodb_ft_server_stopword_table='db/my_stopwords' 更新了 my.cnf。我重新启动 运行:

show variables like 'innodb_ft_server_stopword_table';

带回:

+---------------------------------+---------------------------+
| Variable_name                   | Value                     |
+---------------------------------+---------------------------+
| innodb_ft_server_stopword_table | 'db/my_stopwords'; |
+---------------------------------+---------------------------+

所以我认为 in 现在不会导致查询失败,但它会继续。我也试了一次 OPTIMIZE TABLE table 甚至 ALTER TABLE table DROP INDEX ...ALTER TABLE table ADD FULLTEXT KEY ... none 都有影响。

第二次更新 问题出在停用词上。

$userinput = preg_replace('/\b(a|about|an|are|as|at|be|by|com|de|en|for|from|how|i|in|is|it|la|of|on|or|that|the|this|to|was|what|when|where|who|will|with|und|the|www)\b/', '', $userinput);

解决了这个问题,但对我来说这不是一个好的解决方案。我想要一个解决方案,避免停用词在 mysql.

中打破这个

停用词table数据:

CREATE TABLE `my_stopwords` (
  `value` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1

Name: my_stopwords
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2019-04-09 17:39:55
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options: 
        Comment: 

MyISAM 的 FULLTEXT 和 InnoDB 之间存在一些差异。我认为您被 'short' 词 and/or 停用词的处理抓住了。 MyISAM 将显示行,但 InnoDB 将失败。

我在使用 FT 时(以及切换到 InnoDB 之后)所做的是过滤用户的输入以避免短词。这需要额外的努力,但能让我得到想要的行。我的情况略有不同,因为生成的查询是这样的。请注意,我添加了 + 来要求单词,但不是短于 3 的单词(我的 ft_min_token_size 是 3)。这些搜索是针对 build a tablebuild the table:

WHERE match(description) AGAINST('+build* a +table*' IN BOOLEAN MODE)
WHERE match(description) AGAINST('+build* +the* +table*' IN BOOLEAN MODE)

(后面的 * 可能是多余的;我没有调查过。)

另一种方法

由于 FT 在非短、非停用词方面非常有效,因此分两个阶段进行搜索,每个阶段都是可选的:要搜索 "a long word",执行

WHERE MATCH(d) AGAINST ('+long +word' IN BOOLEAN MODE)
  AND d REGEXP '[[:<:]]a[[:>:]]'

第一部分通过查找 'long' 和 'word'(如 words)快速减少可能的行。第二部分确保字符串中也有 word aREGEXP 成本很高,但只会应用于通过第一次测试的那些行。

只需搜索"long word":

WHERE MATCH(d) AGAINST ('+long +word' IN BOOLEAN MODE)

搜索 只需 字词 "a":

WHERE d REGEXP '[[:<:]]a[[:>:]]'

警告:这种情况会很慢。

注意:我的示例允许单词以任何顺序出现在字符串中的任何位置。也就是说,这个字符串将匹配我的所有示例:"She was longing for a word from him."

这是一个应该重现您的问题的分步过程。 (这实际上是你应该如何写你的问题。)环境是新安装的 VM Debian 9.8Percona Server Ver 5.6.43-84.3.

  1. 创建一个 InnoDB table 带有 全文索引 和一些虚拟数据:

    create table test.ft_innodb (
        txt text,
        fulltext index (txt)
    ) engine=innodb charset=utf8 collate=utf8_unicode_ci;
    
    insert into test.ft_innodb (txt) values
        ('Some dummy text'),
        ('Text with a long and short stop words in it ex');
    
  2. 执行测试查询以验证它是否还不能正常工作:

    select txt
        , match(t.txt) against ('+some' in boolean mode) as score0
        , match(t.txt) against ('+with' in boolean mode) as score1
        , match(t.txt) against ('+in'   in boolean mode) as score2
        , match(t.txt) against ('+ex'   in boolean mode) as score3
    from test.ft_innodb t;
    

    结果(四舍五入):

    txt                                            | score0 | score1 | score2 | score3
    -----------------------------------------------|--------|--------|--------|-------
    Some dummy text                                | 0.0906 | 0      | 0      | 0
    Text with a long and short stop words in it ex | 0      | 0      | 0      | 0
    

    如您所见,它不适用于停用词 ("+with") 或短词 ("+ex")。

  3. 为自定义停用词创建一个空的 InnoDB table:

    create table test.my_stopwords (value varchar(30)) engine=innodb;
    
  4. 编辑 /etc/mysql/my.cnf 并在 [mysqld] 块中添加以下两行:

    [mysqld]
    # other settings
    innodb_ft_server_stopword_table = "test/my_stopwords"
    innodb_ft_min_token_size = 1
    
  5. service mysql restart

  6. 重启MySQL
  7. 运行 再次从 (2.) 查询(结果应该相同)

  8. 重建全文索引
    optimize table test.ft_innodb;
    

    它实际上会重建整个表,包括所有索引。

  9. 再次执行 (2.) 中的测试查询。现在结果是:

    txt                                            | score1 | score1 | score2 | score3
    -----------------------------------------------|--------|--------|--------|-------
    Some dummy text                                | 0.0906 | 0      | 0      | 0
    Text with a long and short stop words in it ex | 0      | 0.0906 | 0.0906 | 0.0906
    

你看它对我来说很好用。而且重现起来非常简单。 (再一次 - 这就是你应该如何写你的问题。)

由于您的程序比较混乱而不是详细,所以很难说您可能会出什么问题。例如:

CREATE TABLE my_stopwords(value VARCHAR(30)) ENGINE = INNODB;

这不包含您在哪个数据库中定义了 table 的信息。请注意,我已将所有 table 前缀为相应的数据库。现在考虑以下内容:我更改 my.cnf 并设置 innodb_ft_server_stopword_table = "db/my_stopwords"。注意 - 我的服务器上没有这样的 table(甚至 db 模式也不存在)。重新启动 MySQL 服务器。并使用

检查新设置
show variables like 'innodb_ft_server_stopword_table';

这个returns:

    Variable_name                   | Value
    --------------------------------|----------------
    innodb_ft_server_stopword_table | db/my_stopwords

并且在 optimize table test.ft_innodb; 测试查询之后 returns 这个:

    txt                                            | score0 | score1 | score2 | score3
    -----------------------------------------------|--------|--------|--------|-------
    Some dummy text                                | 0.0906 | 0      | 0      | 0
    Text with a long and short stop words in it ex | 0      | 0      | 0      | 0.0906

看到了吗?它不再使用停用词。但它适用于短的不间断词,如“+ex”。因此,请确保您在 innodb_ft_server_stopword_table 中定义的 table 确实存在。

一种常见的搜索技术是使用 'sanitized' 字符串创建一个额外的列进行搜索。然后将 FULLTEXT 索引添加到该列而不是原始列。

在您的情况下,删除停用词是主要区别。但也可能有标点符号可以(应该?)删除。有时带连字符的单词或单词或缩写词或零件号或型号会引起麻烦。可以修改它们以更改标点符号或间距,使其更符合 FT 要求 and/or 用户的输入风格。另一件事是将单词添加到搜索字符串列中,这些单词是列中单词的常见拼写错误。

当然,这比您想做的要多。但我认为它提供了一个可行的解决方案。