Silniki wyszukiwania pełnotekstowego, a SphinxSearch z pewnością może do takich się zaliczyć, stworzone zostały z myślą sprawnego i wydajnego wyszukiwania tekstu, niekoniecznie przechowując go jednocześnie. Zatem, typowy scenariusz aplikacji korzystającej z takiego silnika wygląda następująco: 1. wyszukiwanie dokumentów za pomocą silnika wyszukiwania (np. SphinxSearch), 2. pobranie właściwej treści dokumentów (z bazy danych bądź innego źródła ) dla wyników znalezionych w poprzednim kroku.

Inny, często spotykany przypadek, jest taki, iż istniejącą aplikację chcemy rozszerzyć o możliwość wyszukiwania - w tym przypadku konieczne będzie wprowadzenie szeregu zmian, sposób postępowania będzie podobny do tego opisanego powyżej: wyszukiwanie, np. korzystając z API, pobranie wymaganych informacji dla znalezionych wyników. Tutaj z pomocą przychodzi rozwiązanie znane jako SphinxSE.

SphinxSE (ang. Sphinx Storage Engine) to jeden z dostępnych silników przechowywania (ang. storage engine) takich jak np. InnoDb czy też MyISAM. Nie przechowuje on jednak danych sam w sobie a jest jedynie wbudowanym klientem umożliwiającym komunikację ze Sphinxem, wykonywanie zapytań oraz pobieranie wyników. Zaletą tego rozwiązania jest to, że przedstawione powyżej dwa kroki można zredukować do jednego - w jednym kroku wyszukujemy dokumenty oraz pobieramy właściwą treść dokumentu dla znalezionych wyników. Co więcej, także w przypadku rozbudowania istniejącej aplikacji o wyszukiwanie, koszt zmian nie będzie duży - wystarczy jedynie dodać do istniejącego zapytania SQL warunek zgodny ze składnią wyszukiwania SphinxSE i złączyć wyniki ze Sphinxa z rezultatami otrzymanymi przez pierwotne zapytanie do bazy.

ENGINE=SPHINX

Rolę pośrednika pomiędzy search daemonem Sphinxa (searchd) a bazą danych (MySQL, MariaDb) pełni pomocnicza tabela o strukturze:

CREATE TABLE `sphinx_results` (
    `id` bigint(20) NOT NULL,
	`weight` int(11) NOT NULL,
	`query` varchar(3072) NOT NULL,
	`attr1` bigint(20) DEFAULT '0',
	`attr2` bigint(20) DEFAULT '0',
	`attr3` bigint(20) DEFAULT '0',
	`_sph_groupby` int(11) DEFAULT '0',
	`_sph_count` int(11) DEFAULT '0',
	`_sph_distinct` int(11) DEFAULT '0',
	KEY `query` (`query`(1024))
) ENGINE=SPHINX DEFAULT CHARSET=utf8; 

Nazwa tabeli oczywiście jest dowolna, ale konieczne jest aby pierwsze trzy kolumny były następujących typów:

  • pierwsza - INTEGER UNSIGNED lub BIGINT: identyfikator dokumentu
  • druga - INTEGER bądź BIGINT: waga (score) znalezionego dokumentu
  • trzecia - VARCHAR lub TEXT: zapytanie oraz powinna być indeksowana

Pozostałe, dodatkowe kolumny mogą być typów INTEGER, TIMESTAMP, BIGINT, VARCHAR lub FLOAT - będą reprezentowały atrybuty odpowiadające nazwom kolumn (stąd wymagana zgodność typów kolumn dla odpowiadających im atrybutom). Dodatkowo istnieją specjalne kolumny _sph_groupby, _sph_count, _sph_distinct zawierające liczniki dla klauzul @groupby, @count oraz @distinct. Ponadto, dla tabeli-pośrednika trzeba ustawić ENGINE=SPHINX.

Po wysłaniu zapytania i otrzymaniu wyników ze Sphinxa, pomocnicza tabela będzie zawierała znalezione wyniki: identyfikator dokumentu - kolumna id, score wyniku - weight oraz wartości atrybutów w kolejnych kolumnach o nazwach odpowiadającym nazwom atrybutów. Złączając otrzymane wyniki (sphinx_results) z tabelą (lub tabelami) przechowującymi szczegółowe informacje o dokumentach rozwiążemy problem opisany na początku artykułu.

Przykład:

SELECT
	d.`id`,
	d.`title`,
	d.`content`,
	d.`created_on`
FROM
	`sphinx_results` AS sphx
JOIN
	`docs` AS d
ON
	d.`id` = sphx.`id`
WHERE
	sphx.`query` = "@content Lorem ipsum;mode=extended;index=test_sphinxse1;host=127.0.0.1;port=9312;" 

Ze złączenia wyników ze Sphinxa z innymi tabelami będziemy mogli zrezygnować jeśli w indeksie będziemy przechowywali (w postaci atrybutów) także wartości, w których wyszukujemy (np. title, content itd.). W tym celu konieczna jest zmiana konfiguracji indeksu:

#############################################################################
## source definition
#############################################################################
 
source src3 : common
{
	sql_query 		= SELECT id, attr1, attr2, attr3, \
				UNIX_TIMESTAMP(created_on) created_on, \
				title, content FROM docs WHERE \
				id >= $start AND id <= $end
 
	sql_attr_uint 		= attr1
	sql_attr_uint 		= attr2
	sql_attr_uint 		= attr3
	sql_field_string 	= title
	sql_field_string 	= content
	sql_attr_timestamp 	= created_on
}

W tym przypadku nasze zapytanie będzie wyglądało następująco:

SELECT
	`id`,
	`title`,
	`content`,
	`created_on`
FROM
	`sphinx_results`
WHERE
	`query` = "@content Lorem ipsum;mode=extended;index=test_sphinxse2;host=127.0.0.1;port=9312;"; 

Składania zapytań SphinxSE została szczegółowo opisana w dokumentacji, przedstawię jedynie typowe, godne uwagi przypadki:

  • filtrowanie - w pierwszej kolejności wykonywane jest wyszukiwanie dla podanej frazy a następnie uzyskane wyniki są filtrowane po wybranym atrybucie, dlatego warto rozważyć (ze względów optymalizacyjnych) czy nie korzystniej będzie zaindeksować i przeszukiwać pole zawierające atrybut
SELECT
	d.`id`,
	d.`title`,
	d.`content`,
	sphx.`attr1`
FROM
	`sphinx_results` AS sphx
JOIN
	`docs` AS d
ON
	d.`id` = sphx.`id`
WHERE
	sphx.`query` = "@content Lorem ipsum;mode=extended;index=test_sphinxse1;filter=attr1,1;host=127.0.0.1;port=9312;"; 
  • grupowanie - istnieje możliwość grupowania bezpośrednio po atrybucie (groupby=attr:attr_name) bądź korzystając z predefiniowanego ziarna czasowego (jeśli atrybut typu timestamp), np. ziarno dzienne (groupby=day:attr_name), tygodniowe (groupby=week:attr_name) itd. Dodatkowo _sph_count będzie zawierał liczbę wyników w danej grupie
SELECT
	`id`,
	`content`,
	`_sph_count` AS count
FROM
	`sphinx_results`
WHERE
	`query` = "@title sample;mode=extended;index=test_sphinxse2;groupby=attr:content;groupsort=@count desc;host=127.0.0.1;port=9312;"; 
  • escape’owanie wartości specjalnych w zapytaniach - wartości specjalne takie jak np. ; bądź = czy też , (przecinek) escape’ujemy za pomocą trzech / (slash). Dodatkowo jeśli w zapytaniu pojawia się (cudzysłów) a treść całego zapytania SphinxSE (query) zawieramy również pomiędzy ”“, w takim przypadku cudzysłów w szukanej frazie należy poprzedzić siedmioma znakami slash, ponieważ escape’ujemy wyrażenie /” (trzy / dla slasha oraz kolejne trzy dla “). Przykład - szukamy frazy “test” ‘test’ test% @;,=
SELECT
	`id`,
	`title`,
	`content`
FROM
	`sphinx_results`
WHERE
	`query` = "@content \"\\\\\\\"test\\\\\\\" \\\'test\\\' test\\\% \\\@\\\;\\\,\\\=\";mode=extended;index=test_sphinxse2;host=127.0.0.1;port=9312;"; 

Podsumowując, pomimo tego iż SphinxSE nie oferuje pełnych możliwości wyszukiwania za pomocą SphinxSearch oraz wymaga znajomości specyficznej składni zapytań, warto rozważyć wykorzystanie tego rozwiązania w naszej aplikacji. Możemy w prosty i bardzo wydajny sposób, a przy tym nie wymagający dużych nakładów pracy, umożliwić zaawansowane wyszukiwanie w naszym serwisie.

Przydatne linki:

tagi:  ,  ,  ,  ,  ,  , 

Komentarze