Модуль Поиск
Модуль Поиск включает в себя сквозной индекс по разным сущностям, отдельные фильтры по атрибутам сущностей, поддержку стемминга, точный поиск по атрибутам и связанным сущностям.
Но появилась потребность изменить поиск в связи с новым пользовательским сценарием работы. Это новый фильтр, в котором помимо атрибутов (где мы ищем точно), появился произвольный поиск по строке (где пользователь может набирать произвольные комбинации символов). Таким образом, новый поиск:
- должен быть интерактивным в конкретной сущности;
- комбинировать поиск по тексту и фильтр по атрибутам;
- быть быстрым (так называемым поиском «на кончиках пальцев»);
- результатом поиска должно являться текущее представление сущности.
Следовательно, возкникла задача: как эффективно ускорить, изменить и скрестить поиск с фильтрами? Для ее решения было предложено 3 варианта:
- Интегрировать текущий модуль поиска с фильтрами по сущностям - реализовать оказалось непросто и поиск не будет быстрым.
- Перейти на внешний поисковый индекс (Sphinx, Lucene) - результаты тестирования показали хорошую индексацию по атрибутам, тексту, но сложно скрестить результаты поиска с их представлениями в списке. Дополнительная сложность - это десятки и сотни тысяч индексов для облачных версий Битрикс24.
- Полнотекстовый индекс Mysql - оказался подходящим вариантом, о нем поговорим ниже.
Что такое полнотекстовый индекс Mysql?
- Индекс строится по одному или нескольким текстовым полям.
- Mysql разбивает содержимое полей на «слова» и заносит их в отдельные таблицы для построения «обратного» индекса.
- При построении запроса таблицы с индексами прозрачно «джоинятся» к основному запросу.
- Поиск возможен на естественном языке или в режиме
boolean.
Как использовать индекс?
Для этого существует оператор MATCH (col1,col2,...) AGAINST (expr [search_modifier]). У него есть модификаторы, которые позволяют определить нам на каком языке искать:
- IN NATURAL LANGUAGE MODE - на естественном языке;
- IN BOOLEAN MODE - в режиме
boolean; - WITH QUERY EXPANSION - в этом режиме Mysql делает 2 запроса: сначала ищет по первому запросу, находит записи, затем во второй запрос подставляет строчки из первого. Таким образом, сильно расширяется область поиска.
Запрос обычно выглядит следующим образом:
Отметим особенности поиска Mysql:
- При запросе на натуральном языке Mysql сам сортирует результат по релевантности.
- Двойной поиск с параметром WITH QUERY EXPANSION.
- Поиск по умолчанию регистронезависимый.
- В качестве
exprможет использоваться только литерная строка.
При поиск в режиме boolean можно использовать дополнительные параметры:
-
- Должно быть
-
- Не должно быть
- (no operator) Или
- @distance Расстояние
-
~< Вес
- ( ) Группировка
- * Маска (проставляется только справа)
Типичный запрос выглядит так:
Быстродействие поиска Mysql:
- Полнотекстовый индекс существенно оптимизирован: кеширование на модификацию, partitioning (разбиение на несколько таблиц индекса).
- Быстрее LIKE на один-несколько порядков.
- Быстрее нашего модуля поиска в 2-10 раз за счет оптимизации.
- Возможен реально интерактивный поиск.
Требования к поиску:
- Только Innodb (c 5.6) и MyISAM.
- Не поддерживаются таблицы с «партициями».
- Не поддерживаются иероглифы и некоторые кодировки (ucs2).
Использование в продукте
- Создание индекса:
В инсталляторе модуля новый файл install_ft.sql с содержимым:
В install.php код:
$errors = $DB->RunSQLBatch($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/install/mysql/install_ft.sql");
if ($errors === false)
{
$entity = \Bitrix\Main\UserTable::getEntity();
$entity->enableFullTextIndex("SEARCH_USER_CONTENT");
}
```php
"*" => "FT", // partial full text match
"*=" => "FTI", // identical full text match
"*%" => "FTL", // partial full text match based on LIKE
```
-
Необходимо учитывать, что индекса может не быть:
- В новый фильтр ORM добавлена поддержка match: методы whereMatch(), whereNotMatch(), а также хелпер matchAgainstWildcard().
Пример обращения к сущности:
$res = \Bitrix\Main\UserIndexTable::getList(array(
"select" => array("SEARCH_ADMIN_CONTENT"),
"filter" => array(
"*SEARCH_ADMIN_CONTENT" => \Bitrix\Main\Search\Content::prepareStringToken("vad dumbrav"),
)
));
var_dump(\Bitrix\Main\Entity\Query::getLastQuery())
Запрос:
string(216) "SELECT
`main_user_index`.`SEARCH_ADMIN_CONTENT` AS `SEARCH_ADMIN_CONTENT`
FROM `b_user_index` `main_user_index`
WHERE MATCH (`main_user_index`.`SEARCH_ADMIN_CONTENT`) AGAINST ('(+inq* +qhzoeni*)' IN BOOLEAN MODE)";
Более медленный запрос с LIKE:
string(277) "SELECT
`main_user_index`.`SEARCH_ADMIN_CONTENT` AS `SEARCH_ADMIN_CONTENT`
FROM `b_user_index` `main_user_index`
WHERE ((UPPER(`main_user_index`.`SEARCH_ADMIN_CONTENT`) like '%INQ%' ESCAPE '!' AND UPPER(`main_user_index`.`SEARCH_ADMIN_CONTENT`) like '%QHZOENI%' ESCAPE '!'))"
Особенности по использованию полнотекстового поиска:
- Минимальная длина слова в индексе (по умолчанию 3). Параметр соединения ft_min_token_size.
- Словарь стоп-слов. Можно или отключить, или преобразовывать данные методом Bitrix\Main\Search\Content::prepareStringToken().
- Поиск по части слова справа (звездочка слева). Можно декомпозировать не длинные слова: 123456789, 23456789, 3456789…