Я обновил таблицу с 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
и снова запустил оптимизацию, но снова получил тот же результат.
Единственная разница между этими таблицами — это движок и набор символов. В этой таблице используется utf8
, в таблице myisam
используется 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
приводит к сбою, но это следующее слово в моей таблице.
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:
@PaulSpiegel Это список из 14 столбцов. Должен ли я добавить это или просто добавить утверждение create table
?
Вам не нужно 14 столбцов, чтобы воспроизвести проблему. Создайте пример таблицы с двумя строками, двумя столбцами и значениями, которые имеют ту же проблему.
@PaulSpiegel Я думаю, что это демонстрирует проблему. db-fiddle.com/f/t9iXPrxq1ZwPa6CPzdiL6M/2db-fiddle.com/f/t9iXPrxq1ZwPa6CPzdiL6M/3
Вы не можете продемонстрировать это на DB-fiddle, так как вы не можете перезаписывать глобальные переменные или переменные только для чтения. Я протестировал его на своем локальном компьютере с версией 5.6.21 и не смог воспроизвести вашу проблему. У меня есть ненулевое значение для «+Term +Ex +In». Вот почему я попросил создать MCVE.
ОК .. На самом деле вы можете перезаписать глобальную переменную в db-fiddle - но здесь я смог использовать стоп-слово («с»).
@PaulSpiegel in
все еще не соответствует этому примеру, поэтому я думаю, что это воспроизводимо. db-fiddle.com/f/pHFaCD5YCpSEgXAo4F3vJQ/2 Я не могу изменить полные текстовые базы данных на этом сервере.
Вы написали, что устранили проблему с innodb_ft_min_token_size
, так как +Ex
у вас работает. Насколько я понимаю, проблема только с +In
. А так как +Ex
работает, проблема должна быть со стоп-словами. Но я продемонстрировал, что со стоп-словами можно работать. Итак, еще раз - я не могу воспроизвести вашу проблему.
@PaulSpiegel Это было мое предположение. Я открыт для любых / всех предложений. A
также сломал мой поиск, например. +A +Term +Ex
поэтому только стоп-слова приводят к сбою поиска. Он согласован между 2 ведомыми и главной БД. Он запускает экземпляр Percona 5.6.41-84.1
, если это имеет значение
Пробовали ли вы использовать более длинные стоп-слова, такие как with
или where
?
@PaulSpiegel Да, поиск AGAINST ('+with' IN BOOLEAN MODE);
возвращает 0 результатов. Aselect count(*) from table where concat(columns) like '%with%';
возвращает 10468.
В вашем первом обновлении, какой текст вы надеялись сопоставить?
@RickJames Term Ex
был первым термином, который я пытался сопоставить. Уменьшение длины решило это. Term Ex in
тогда не удалось
Есть несколько различий между FULLTEXT MyISAM и InnoDB. Я думаю, вы были пойманы на обработке «коротких» слов и/или стоп-слов. 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 очень эффективен для некоротких, непрерывных слов, выполняйте поиск в два этапа, каждый из которых является необязательным: Чтобы найти «длинное слово», выполните
WHERE MATCH(d) AGAINST ('+long +word' IN BOOLEAN MODE)
AND d REGEXP '[[:<:]]a[[:>:]]'
Первая часть быстро сокращает возможные строки, ища «длинное» и «слово» (как слова). Вторая часть также проверяет, есть ли в строке словоa
. REGEXP
стоит дорого, но будет применяться только к тем строкам, которые проходят первый тест.
Для поиска только что по "длинному слову":
WHERE MATCH(d) AGAINST ('+long +word' IN BOOLEAN MODE)
Для поиска только что по слову "a":
WHERE d REGEXP '[[:<:]]a[[:>:]]'
Предостережение: этот случай будет медленным.
Примечание. В моих примерах слова могут быть в любом порядке и в любом месте строки. То есть эта строка будет совпадать во всех моих примерах: «Она ждала от него слова».
Это то, к чему привело и мое исследование. Однако нет способа сделать стоп-слово a
непрерывным? Я хочу, чтобы он был проиндексирован. Сейчас я использую похожее решение с регулярным выражением для построения строки запроса, но я бы предпочел просто передать все это с помощью +
для каждого условия. Мои правила поиска заключаются в том, что должны присутствовать все введенные термины.
@ user3783243 - Не существует «идеального» решения того, что вы хотите. Я добавил в свой ответ обходной путь, который может быть «лучшим» решением. Обратите внимание, что это требует построения предложения WHERE
на основе встречающихся слов и может потребовать знания списка стоп-слов.
Это правда, но регулярное выражение будет работать без индекса и без релевантности.
Опять же, нет «идеального» решения.
Значит, нельзя игнорировать стоп-слова? Это кажется идеальным решением, и myisam
предложил его. Кажется, что innodb
сделал шаг назад в этом отношении для меня. Я только начинаю с нами innodb
, хотя хотел бы подтвердить, что это правильно. Из руководства и других тем SO кажется, что пользовательская таблица возможна.
@user3783243 user3783243 - Я думаю, что есть способ предоставить собственный файл списка стоп-слов (который может быть пустым), но у меня нет подробностей.
Я посмотрел на dev.mysql.com/doc/refman/5.6/en/… и следовал там директивам, а также к версии 8.0, но ни один из них не работал у меня.
@user3783243 user3783243 - Я подозреваю, что где-то произошла простая ошибка ... Вы перестроили индекс FT в своей таблице? Укажите SHOW CREATE TABLE
и SHOW TABLE STATUS
для таблицы стоп-слов.
Данные добавлены к вопросу
Вот пошаговая процедура, которая должна была воспроизвести вашу проблему. (На самом деле именно так вы должны были написать свой вопрос.) Среда представляет собой недавно установленную виртуальную машину с Дебиан 9.8 и Сервер Percona версии 5.6.43-84.3.
Создайте таблицу ИнноБД с полнотекстовый индекс и некоторыми фиктивными данными:
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").
Создайте пустую таблицу ИнноБД для пользовательских стоп-слов:
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
Перезапустите MySQL с помощью service mysql restart
Запустите запрос из (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;
Это не содержит информации о том, в какой базе данных вы определили эту таблицу. Обратите внимание, что я поставил перед всеми своими таблицами префикс соответствующей базы данных. Теперь рассмотрим следующее: я меняю 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;
перед выполнением запроса.
Красиво написанный вопрос!
Распространенным методом поиска является создание дополнительного столбца с «очищенной» строкой для поиска. Затем добавьте к этому столбцу индекс FULLTEXT вместо исходного столбца.
В вашем случае удаление стоп-слов является основным отличием. Но могут быть и знаки препинания, которые можно (должны?) убрать. Иногда проблемы вызывают слова, написанные через дефис, или слова, или сокращения, или номера деталей, или номера моделей. Их можно изменить, чтобы изменить пунктуацию или интервалы, чтобы сделать их более подходящими для требований FT и / или вкуса ввода пользователя. Другое дело — добавить в столбец строки поиска слова, которые являются распространенными ошибками в написании слов, содержащихся в столбце.
Конечно, это больше работы, чем вам хотелось бы. Но я думаю, что это обеспечивает жизнеспособное решение.
Пожалуйста, также опубликуйте значение
columns
(или значения, если это составной индекс).