全文搜索 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 正在使用 utf8
,myisam
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 table
和 build 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 a
。 REGEXP
成本很高,但只会应用于通过第一次测试的那些行。
只需搜索"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.8 和 Percona Server Ver 5.6.43-84.3.
创建一个 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');
执行测试查询以验证它是否还不能正常工作:
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")。
为自定义停用词创建一个空的 InnoDB table:
create table test.my_stopwords (value varchar(30)) engine=innodb;
编辑 /etc/mysql/my.cnf
并在 [mysqld]
块中添加以下两行:
[mysqld]
# other settings
innodb_ft_server_stopword_table = "test/my_stopwords"
innodb_ft_min_token_size = 1
用service mysql restart
重启MySQL
运行 再次从 (2.) 查询(结果应该相同)
用
重建全文索引
optimize table test.ft_innodb;
它实际上会重建整个表,包括所有索引。
再次执行 (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 用户的输入风格。另一件事是将单词添加到搜索字符串列中,这些单词是列中单词的常见拼写错误。
当然,这比您想做的要多。但我认为它提供了一个可行的解决方案。
我已将 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 正在使用 utf8
,myisam
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 table
和 build 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 a
。 REGEXP
成本很高,但只会应用于通过第一次测试的那些行。
只需搜索"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.8 和 Percona Server Ver 5.6.43-84.3.
创建一个 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');
执行测试查询以验证它是否还不能正常工作:
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")。
为自定义停用词创建一个空的 InnoDB table:
create table test.my_stopwords (value varchar(30)) engine=innodb;
编辑
/etc/mysql/my.cnf
并在[mysqld]
块中添加以下两行:[mysqld] # other settings innodb_ft_server_stopword_table = "test/my_stopwords" innodb_ft_min_token_size = 1
用
service mysql restart
重启MySQL
运行 再次从 (2.) 查询(结果应该相同)
用
重建全文索引optimize table test.ft_innodb;
它实际上会重建整个表,包括所有索引。
再次执行 (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 用户的输入风格。另一件事是将单词添加到搜索字符串列中,这些单词是列中单词的常见拼写错误。
当然,这比您想做的要多。但我认为它提供了一个可行的解决方案。