【问题标题】:FullText Search Innodb Fails, MyIsam Returns Results全文搜索 Innodb 失败,MyIsam 返回结果
【发布时间】:2019-04-09 15:00:06
【问题描述】:

我已将表从 myisam 升级到 innodb,但性能不一样。当应该存在某种关系时,innodb 返回一个0 分数。 myisam 表返回相同术语的匹配项(我保留了旧表的副本,因此我仍然可以运行相同的查询)。

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

返回:

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

但是:

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

返回:

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

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

这些表之间的唯一区别是引擎和字符集。此表使用utf8myisam 表使用latin1

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

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

优化 |注意 |表不支持优化,做recreate+analyze代替

此更改后查询有效。 Ex 不是停用词,但不知道为什么它会有所作为。

一个失败的新查询是:

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

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

in 导致此操作失败,但这是我表中的下一个单词。

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;,然后将my.cnf 更新为innodb_ft_server_stopword_table='db/my_stopwords'。我重新启动并运行:

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 ... 都没有产生影响。

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

$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 中破坏它的解决方案。

停用词表数据:

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: 

【问题讨论】:

  • 请同时发布columns 的值(或值 - 如果它是一个复合索引)。
  • @PaulSpiegel 是 14 列的列表。我应该添加它,还是只添加一个create table 声明?
  • 重现问题不需要 14 列。创建一个包含两行、两列和值的示例表,它们具有相同的问题。
  • @PaulSpiegel 我认为这些证明了这个问题。 db-fiddle.com/f/t9iXPrxq1ZwPa6CPzdiL6M/2db-fiddle.com/f/t9iXPrxq1ZwPa6CPzdiL6M/3
  • 您无法在 DB-fiddle 上演示它,因为您无法覆盖全局或只读变量。我已经在本地机器上使用 v5.6.21 对其进行了测试 - 并且无法重现您的问题。 “+Term +Ex +In”有一个非零值。这就是为什么我要求创建一个MCVE

标签: mysql full-text-search innodb myisam mysql-5.6


【解决方案1】:

MyISAM 的 FULLTEXT 和 InnoDB 之间有几个不同之处。我认为您被“短”词和/或停用词的处理所吸引。 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 在非短、非停用词方面非常有效,因此分两个阶段进行搜索,每个阶段都是可选的:要搜索“长词”,请这样做

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

第一部分通过查找“long”和“word”(作为words)快速减少可能的行。第二部分确保字符串中也有一个 word aREGEXP 的开销很大,但只会应用于通过第一次测试的那些行。

搜索“长词”:

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

搜索单词“a”:

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

警告:这种情况会很慢。

注意:我的示例允许单词按任何顺序排列,并且位于字符串中的任何位置。也就是说,这个字符串在我所有的例子中都会匹配:“She was longing for a word from him.”

【讨论】:

  • 这也是我的研究成果。有没有办法让a 停用词成为非停用词?我希望它被索引。我现在使用带有正则表达式的类似解决方案来构造查询字符串,但我更愿意在每个术语上都使用+s 将其全部传递。我的搜索规则是所有输入的词都必须存在。
  • @user3783243 - 没有你想要的“完美”解决方案。我在我的答案中添加了一个可能是“最佳”解决方案的解决方法。请注意它需要如何根据遇到的单词构造WHERE 子句,并且可能需要知道停用词列表。
  • 确实如此,但正则表达式将在没有索引且没有相关性的情况下执行。
  • 再一次,没有“完美”的解决方案。
  • 那么有没有办法忽略停用词呢?这似乎是完美的解决方案,myisam 提供了它。似乎innodb 在这方面倒退了一步。我才刚刚开始innodb,但我想确认这是正确的。从手册和其他 SO 线程来看,自定义表似乎是可能的。
【解决方案2】:

这是一个逐步的过程,应该可以重现您的问题。 (这实际上是你应该如何写你的问题。)环境是一个新安装的虚拟机,带有 Debian 9.8Percona Server Ver 5.6.43-84.3

  1. 使用 全文索引 和一些虚拟数据创建一个 InnoDB 表:

    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 表:

    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重启MySQL

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

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

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

  8. 再次执行 (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;

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

show variables like 'innodb_ft_server_stopword_table';

这会返回:

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

optimize table test.ft_innodb; 之后,测试查询返回:

    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 中定义的表确实存在。

【讨论】:

  • 该表在没有完全限定语句的情况下存在。它使用了我已经在其中的数据库。运行第 3 步产生了ERROR 1050 (42S01): Table 'my_stopwords' already exists。第 2 步还带回了 score3 的分数(因为我已经减少了我猜的索引长度)。第 8 步的结果相同。
  • “它使用了我已经在其中的数据库” - 数据库的名称是什么? db?
  • 在示例中是的,db 是数据库名称。我在查询执行之前使用use db;
  • 写得很好的问题!
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 2018-01-21
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多