358  /  384

Полнотекстовый поиск

Просмотров: 1498 (Статистика ведётся с 06.02.2017)
Татьяна Старкова
Сложность урока:
4 уровень - сложно, требуется сосредоточится, внимание деталям и точному следованию инструкции.
1
2
3
4
5
Недоступно в редакциях:
Ограничений нет

Модуль Поиск существует очень давно и включает в себя сквозной индекс по разным сущностям, отдельные фильтры по атрибутам сущностей, поддержку стемминга, точный поиск по атрибутам и связанным сущностям.

Но появилась потребность изменить поиск в связи с новым пользовательским сценарием работы. Это новый фильтр, в котором помимо атрибутов (где мы ищем точно), появился произвольный поиск по строке (где пользователь может набирать произвольные комбинации символов). Таким образом, новый поиск:

  • должен быть интерактивным в конкретной сущности;
  • комбинировать поиск по тексту и фильтр по атрибутам;
  • быть быстрым (так называемым поиском «на кончиках пальцев»);
  • результатом поиска должно являться текущее представление сущности.

Следовательно, возкникла задача: как эффективно ускорить, изменить и скрестить поиск с фильтрами? Для ее решения было предложено 3 варианта:

  1. Интегрировать текущий модуль поиска с фильтрами по сущностям - реализовать оказалось непросто и поиск не будет быстрым.
  2. Перейти на внешний поисковый индекс (Sphinx, Lucene) - результаты тестирования показали хорошую индексацию по атрибутам, тексту, но сложно скрестить результаты поиска с их представлениями в списке. Дополнительная сложность - это десятки и сотни тысяч индексов для облачных версий Битрикс24.
  3. Полнотекстовый индекс Mysql - оказался подходящим вариантом, о нем поговорим ниже.

Что такое полнотекстовый индекс Mysql?

  • Индекс строится по одному или нескольким текстовым полям.
  • Mysql разбивает содержимое полей на «слова» и заносит их в отдельные таблицы для построения «обратного» индекса.
  • При построении запроса таблицы с индексами прозрачно «джоинятся» к основному запросу.
  • Поиск возможен на естественном языке или в режиме boolean.

Как использовать индекс?

Для этого существует оператор MATCH (col1,col2,...) AGAINST (expr [search_modifier]). У него есть модификаторы, которые позволяют определить нам на каком языке искать:

  • IN NATURAL LANGUAGE MODE - на естественном языке;
  • IN BOOLEAN MODE - в режиме boolean;
  • WITH QUERY EXPANSION - в этом режиме Mysql делает 2 запроса: сначала ищет по первому запросу, находит записи, затем во второй запрос подставляет строчки из первого. Таким образом, сильно расширяется область поиска.

Запрос обычно выглядит следующим образом:

SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('database' IN NATURAL LANGUAGE MODE);

Отметим особенности поиска Mysql:

  • При запросе на натуральном языке Mysql сам сортирует результат по релевантности.
  • Двойной поиск с параметром WITH QUERY EXPANSION.
  • Поиск по умолчанию регистронезависимый.
  • В качестве expr может использоваться только литерная строка.

При поиск в режиме boolean можно использовать дополнительные параметры:

  • + Должно быть
  • - Не должно быть
  • (no operator) Или
  • @distance Расстояние
  • >~< Вес
  • ( ) Группировка
  • * Маска (проставляется только справа)

Типичный запрос выглядит так:

SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);

Быстродействие поиска Mysql:

  • Полнотекстовый индекс существенно оптимизирован: кеширование на модификацию, partitioning (разбиение на несколько таблиц индекса).
  • Быстрее LIKE на один-несколько порядков.
  • Быстрее нашего модуля поиска в 2-10 раз за счет оптимизации.
  • Возможен реально интерактивный поиск.

Требования к поиску:

  • Только Innodb (c 5.6) и MyISAM.
  • Не поддерживаются таблицы с «партициями».
  • Не поддерживаются иероглифы и некоторые кодировки (ucs2).

Использование в продукте

  1. Создание индекса:

    В инсталляторе модуля новый файл install_ft.sql с содержимым:

    CREATE fulltext index IXF_B_USER_INDEX_1 on b_user_index (SEARCH_USER_CONTENT);
    
    В 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");			
    }
    
  2. Индексирование:
    • Можно создать либо текстовую колонку в таблице, либо связанную отдельную таблицу (предпочтительнее для больших существующих таблиц).
    • В индексированную колонку можно записывать объединенные данные с разных колонок (искать «везде»).
    • На одном хите практически невозможно ни заполнить эту колонку, ни построить индекс по имеющимся данным.
    • Рекомендуем пошаговый индексатор на агентах.
  3. Индексатор:
    • Базовый класс Bitrix\Main\Update\Stepper запускает агенты и предоставляет графический интерфейс.
    • Необходимо реализовать метод execute(), который выполняет реальную работу в пошаговом режиме.
    • Метод getHtml() предоставляет интерфейс с аяксовым «хитователем» - желтый прогресс-индикатор.
    • Метод bind() добавляет агента (в обновлении).
  4. Использование:
    • Добавлены новые операторы в построитель запросов:
      "*" => "FT", // partial full text match
      "*=" => "FTI", // identical full text match
      "*%" => "FTL", // partial full text match based on LIKE
      
    • Необходимо учитывать, что индекса может не быть:
      $operation = (LogIndexTable::getEntity()->fullTextIndexEnabled("CONTENT")? '*' : '*%‘);
      
    • В новый фильтр 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…

Итоги по применению полнотекстового поиска:

  • Полнотекстовый поиск хорошо показал себя в облачном Битрикс24. Множество сущностей уже используют поиск.
  • В 1C-Битрикс: Управление сайтом работает пока только в списке пользователей.
  • Хорошо показал себя в плане нагрузки, значительно легче, чем модуль поиска или фильтрация по LIKE.


2
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии