Sphinx - Полезные полезности
В процессе изучения Sphinx, я периодически сталкиваюсь с некоторыми его особенностями, узнать о которых можно либо подробно изучив документацию, либо опытным путём. Об этих особенностях полезно помнить, но все они едва ли заслуживают отдельной статьи, поэтому я решил собрать некоторые из них вместе.
Кодировки
Во всех примерах, которые я приводил в предыдущих статьях, мы индексировали текст, содержащий только латинские символы. Если же мы будем индексировать текст, содержащий ещё и кириллические символы, то подключившись к Sphinx, например при помощи консольного клиента для MySQL, мы получим результат в неправильной кодировке.
Результат может выглядеть так:
Или так:
Решается эта проблема достаточно просто. Во-первых, в файле конфигурации необходимо при помощи
опции charset_type
указать туже кодировку, которая используется в источнике данных, а во-вторых, эту же кодировку
необходимо указать при подключении к Sphinx. При использовании в качестве источника базы данных
MS SQL Server, в которой данные хранятся в кодировке unicode, также необходимо использовать
опцию mssql_unicode
.
Например, если мы индексируем базу данных MS SQL Server, где данные хранятся в столбцах с типами
данных char
, varchar
или text
то в опции
charset_type
необходимо указывать значение sbcs
(или вообще ничего не
указывать, поскольку это значение используется по умолчанию), а при подключении к Sphinx
указывать кодировку windows-1251:
c:\mysql\bin\mysql -h 127.0.0.1 -P 9306 --default-character-set=cp1251
Если же мы индексируем xml-файл в кодировке utf-8 (индексировать xml-файлы можно только в этой
кодировке) или базу данных MS SQL Server, где данные хранятся в столбцах с типами данных
nchar
, nvarchar
или ntext
то в опции
charset_type
необходимо указывать значение utf-8
, в опции
mssql_unicode
- значение 1
(только для MS SQL Server), а при
подключении к Sphinx указывать кодировку utf-8:
c:\mysql\bin\mysql -h 127.0.0.1 -P 9306 --default-character-set=utf8
Указывание кодировки при подключении к Sphinx относится не только к консольному клиенту для MySQL, но и к любому коннектору, который вы захотите использовать. Например, при использовании коннектора для .NET, кодировка в строке подключения указывается так:
Server=127.0.0.1;Port=9306;Character Set=utf8
После того, как мы правильно укажем все настройки, результат будет выглядеть следующим образом:
Опция «max_matches»
В Sphinx имеется ограничение на максимальное количество найденных документов, которое может храниться в оперативной памяти для каждого запроса. Это означает, что при поиске Sphinx будет искать совпадения во всех документах, но только самые лучшие совпадения будут хранится и впоследствии смогут быть возвращены клиенту.
Задаётся это ограничение в файле конфигурации в блоке searchd
при помощи опции
max_matches
:
searchd
{
...
max_matches = 10000
}
Если в файле конфигурации эта опция не указана, то она считается равной 1000.
Помимо файла конфигурации опция max_matches
может быть задана для каждого запроса в
отдельности при помощи выражения option
:
select * from product order by price desc limit 100, 20 option max_matches = 10000;
При этом значение опции max_matches
, заданное для конкретного запроса, не может
превышать то значение, которое задано в файле конфигурации (или значение по умолчанию, если в
файле конфигурации оно не указано).
Например, если предположить, что в запросе выше в индексе содержится 50000 товаров, то Sphinx
выберет 10000 товаров, отсортированных по цене. Поскольку используется сортировка по убыванию,
то выбраны будут только товары с высокими ценами. Товары с низкими ценами выбраны не будут,
поскольку при такой сортировке они выходят за ограничение на максимальное количество совпадений.
После того, как 10000 товаров выбраны, к ним будет применён оператор limit
, который
ещё больше ограничит выборку, вернув только 20 товаров, начиная с 100-ого по порядку товара.
Стоит также отметить, что данная опция может очень сильно влиять на производительнось, поэтому следует с осторожностью использовать значения, которые превышают значение по умолчанию.
Команда «show meta»
Иногда бывает необходимо вместе с результатами запроса получить дополнительную информацию о его
выполнении, например, время выполнения или общее количество найденных документов. Подобную
информацию по последнему выполненному запросу позволяет получить специальная команда
show meta
.
select * from product where match('men sports shorts');
show meta;
Выполненная сразу после другой команды, команда show meta
возвращает таблицу с
несколькими значениями.
Значения total_found и total содержат количество документов, найденных в индексе. Разница между ними заключается в том, что total учитывает только те документы, которые остались после применения огранияения max_matches, поэтому это значение не может больше значения max_matches.
Значение time содержит время выполнения запроса в секундах.
Значения keyword, docs и hits с индексами содержат слова, по которым
производился поиск, количество документов, в которых это слово упоминается и полное количество
упоминаний слова во всех документах (одно и тоже слово может упоминаться в документе несколько
раз). Обратите внимание, что поиск может выполняться не по тому слову, которое указано в
операторе match
, а по его нормализованной форме. Например, в запросе выше мы искали
по словам «sports» и «shorts», но команда show meta
вернула нам слова «sport» и
«short». Процесс нормализации слов называется стеммингом и нужен, чтобы Sphinx мог
находить слова в разных словоформах.
Команда «call keywords»
В некоторых случаях может потребоваться узнать, как Sphinx «видит» поисковую фразу: как
разбивает её на слова, как эти слова нормализуются и сколько раз каждое слово встречается в
индексе. Получить эту статистику можно и с помощью команды show meta
, однако, если
результаты поиска при этом не нужны, проще воспользоваться командой
call keywords
.
call keywords('men sports shorts', 'product', 1);
Эта команда получает три параметра: поисковую фразу, название индекса и флаг, показывающий, нужно ли возвращать колонки docs и hits.
Команды «show tables» и «describe»
Ещё две полезные команды, которые могут потребоватьбся, чтобы получить информацию о существующих
индексах: show tables
и describe
.
Команда show tables
возвращает список существующих индексов вместе с их типами:
локальные (local), индексы реального времени (rt), распределённые (distributed).
show tables;
Команда describe
возвращает список полей и атрибутов в указанном индексе.
describe product;
Про кодировки - не столь важно, как они выглядят на экране (в конце концов, никто не мешает в клиенте выполнить set names=...). Гораздо важнее указать при индексации правильный charset_type!
ОтветитьУдалитьИсторически всё было в однобайтных кодировках, и потому по дефолту charset_type в сфинксе - sbcs (single-byte character set). При этом автоматически работает встроенный charset_table, который переваривает латинницу и русский язык в кодировке cp1251 (виндовое прошлое!). Остальные символы при токенизации считаются разделителями (пробелами). Это всё работает прозрачно, так что обычному пользователю вроде как вообще незачем трогать charset_type и charset_table (они кажутся непонятными и большими. Вроде как бестолку загромождают конфиг). Но годы летят, прогресс не стоит на месте - и в настоящий момент работа сфинкса с другими языками (кроме только русского и английского) стала вполне обычной. На линуксе сейчас гораздо чаще встретишь данные в utf-8, чем в однобайтной кодировке!
И вот тут появляются грабли: если задать в конфиге источник, который индексирует данные в utf-8, но при этом забыть явно указать charset_type=utf-8 - да, индекс создастся. И даже будет что-то искать. Но ооо-чень медленно! Грабли настолько часты, что даже породили миф - дескать, "сфинкс тормозит!" А механизм этих "тормозов" очень прост: сфинкс пытается токенизировать поток utf-8 с помощью кодировки 1251. Байты, случайно совпавшие с русскими буквами (у вас, кстати, можно разглядеть их в скриншоте дампа) индексируются. А не являющиеся - считаются пробелами и разделяют слова. В итоге по факту получается индекс из нескольких однобуквенных "слов" русского алфавита, которые можно разглядеть в потоке utf-8. А поиск в сфинксе заточен именно на разные слова в словаре. Они образуют большой словарь с хэшем, который позволяет найти любое слово практически мгновенно. А вот дальше - по найденным словам извлекаются цепочки документов и хитов в документах, которые уже как-то взаимодействуют между собой и образуют финальный резултсет. При этом поток документов/хитов по сути обрабатывается линейно, штука за штукой, а это гораздо дОльше. Представьте, что если вместо поиска документов, где есть слово "мир" (операция "в лоб" - извлекаем из словаря цепочку документов и мгновенно выдаём результат) вы делаете три поиска - на "м", "и" и "р", и потом пересекаете результаты? Такой поиск мало того, что очень медленный, но ещё и не всегда точен (у некоторых букв и составленных из них слов в дампе utf-8 может вообще не оказаться ни одного русского символа. И тогда они вообще пропадают из поиска).
В общем - всего одна строчка, зато какой эффект!
Интересно пишите, Игорь. Есть вопрос по поводу SphinxQL.
ОтветитьУдалитьЯ поставил себе sphinx на локальный компьютер чтобы попрактиковаться. Не сразу, но все заработало (в том числе благодаря вашим статьям). Но меня смущает одна вещь. У меня одновременно на локальном сайте работает либо запросы к MySQL (searchd.exe отключен), либо к SphinxQL (searchd.exe включен). Но сайт-то пользуется и тем и другим. И если на локальной машине я могу вкл/выкл. searchd.exe когда нужно, то на для внешнего сервера это однозначно абсурд.
Это так и должно быть и я должен выбрать какой-то один способ работы с БД или я сделал что-то не так?
п/с. Я так понимаю все из-за listen = 127.0.0.1:3306:mysql41.
Порт 3306 по умолчанию используется для бд mysql, поэтому она со сфинксом не могут этот порт использовать одновременно.
ОтветитьУдалитьНужно сфинксу назначить любой другой порт, обычно везде указывают 9603:
listen = 127.0.0.1:9603:mysql41
Думаю, дело в этом.
А лучше взять 9306 для sphinxql и 9312 для api. Это официальные порты сфинкса в IANA (см. http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt)
ОтветитьУдалитьБольшое спасибо за комментарий, при неправильном указании charset_type действительно ищет всякую чушь, я этого раньше не замечал, поскольку всегда и везде использовал utf-8. Поправил статью.
ОтветитьУдалитьset names относится только к mysql, в mssql нельзя при подключении указывать кодировку, т.к. там разные столбцы сами по себе могут иметь разные кодировки, поэтому там используется опция mssql_unicode.
Тем не менее, как я понял, если в источнике данных используется определённая кодировка, то её надо использовать везде: и в charset_type и при подключении к sphinx. Нельзя, например, проиндексировать источник данных в windows-1251, а индекс сохранить в utf-8.
Даже не кодировка, а именно _тип_ кодировки. Их, в общем-то, как раз ровно два.
ОтветитьУдалитьНа самом деле можно и utf-8 проиндексировать как однобайтовую - но важно тогда в charset-table указать все его видимые символы. Правда, при этом не будут работать стеммеры/лемматизаторы, поскольку они такой финт просто не поймут.
Ну и правильность кодировки можно всегда проверить, выполнив сразу после запроса команду show meta. Там будет видно либо реальные слова, либо одно-двухбуквенные ошмётки
Здравствуйте! Скажите, как поступить в случае с MS SQL базой, в которой есть как поля таблиц как nvarchar, так и varchar. при коннекшн --default-character-set=utf8 отображается правильно только часть таблиц. А при --default-character-set=cp1251 - не ищет кириллицу
ОтветитьУдалитьПри индексации в sql запросах делать приведение к nvarchar.
ОтветитьУдалитьselect ID, cast(Name as nvarchar) as Name from ...
С учётом того, что последние вевсии сфинкса поддерживают только utf-8, это будет правильнее всего.
Спасибо огромное, действительно помогло, очень выручили
ОтветитьУдалить