sql >> Databáze >  >> RDS >> MariaDB

Úvod do fulltextového vyhledávání v MariaDB

Databáze jsou určeny k efektivnímu ukládání a dotazování dat. Problém je v tom, že existuje mnoho různých typů dat, která můžeme uložit:čísla, řetězce, JSON, geometrická data. Databáze používají různé metody k ukládání různých typů dat – struktura tabulek, indexy. Ne vždy stejný způsob ukládání a dotazování na data je efektivní pro všechny jeho typy, takže je docela obtížné použít jedno univerzální řešení. V důsledku toho se databáze snaží používat různé přístupy pro různé typy dat. Například v MySQL nebo MariaDB máme generické, dobře fungující řešení jako InnoDB, které ve většině případů funguje dobře, ale máme také samostatné funkce pro práci s daty JSON, samostatné prostorové indexy pro urychlení dotazování na geometrická data nebo fulltextové indexy. , pomáhá s textovými daty. V tomto blogu se podíváme na to, jak lze MariaDB použít k práci s fulltextovými daty.

Pro urychlení hledání textových dat lze také použít běžné indexy B+Tree v InnoDB. Hlavním problémem je, že díky své struktuře a povaze mohou pomoci pouze s hledáním předpon nejvíce vlevo. Je také drahé indexovat velké objemy textu (což vzhledem k omezením předpony zcela vlevo nedává smysl). Proč? Podívejme se na jednoduchý příklad. Máme následující větu:

„Rychlá hnědá liška skáče přes líného psa“

Pomocí běžných indexů v InnoDB můžeme indexovat celou větu:

„Rychlá hnědá liška skáče přes líného psa“

Jde o to, že když hledáme tato data, musíme vyhledat úplný prefix úplně vlevo. Takže dotaz jako:

SELECT text FROM mytable WHERE sentence LIKE “The quick brown fox jumps”;

Bude mít prospěch z tohoto indexu, ale dotaz jako:

SELECT text FROM mytable WHERE sentence LIKE “quick brown fox jumps”;

Nebude. V rejstříku není žádná položka, která by začínala „rychle“. V rejstříku je položka, která obsahuje „rychlé“, ale začíná od „The“, takže ji nelze použít. V důsledku toho je prakticky nemožné efektivně dotazovat textová data pomocí indexů B+Tree. Naštěstí jak MyISAM, tak InnoDB implementovaly FULLTEXT indexy, které lze použít ke skutečné práci s textovými daty na MariaDB. Syntaxe je mírně odlišná než u běžných SELECTů, pojďme se podívat, co s nimi můžeme dělat. Pro data jsme použili náhodný indexový soubor z výpisu databáze Wikipedie. Struktura dat je následující:

617:11539268:Arthur Hamerschlag
617:11539269:Rooster Cogburn (character)
617:11539275:Membership function
617:11539282:Secondarily Generalized Tonic-Clonic Seizures
617:11539283:Corporate Challenge
617:11539285:Perimeter Mall
617:11539286:1994 St. Louis Cardinals season

Ve výsledku jsme vytvořili tabulku se dvěma BIG INT sloupci a jedním VARCHAR.

MariaDB [(none)]> CREATE TABLE ft_data.ft_table (c1 BIGINT, c2 BIGINT, c3 VARCHAR, PRIMARY KEY (c1, c2);

Poté jsme načetli data:

MariaDB [ft_data]> LOAD DATA INFILE '/vagrant/enwiki-20190620-pages-articles-multistream-index17.txt-p11539268p13039268' IGNORE INTO  TABLE ft_table COLUMNS TERMINATED BY ':';
MariaDB [ft_data]> ALTER TABLE ft_table ADD FULLTEXT INDEX idx_ft (c3);
Query OK, 0 rows affected (5.497 sec)
Records: 0  Duplicates: 0  Warnings: 0

Vytvořili jsme také FULLTEXTOVÝ index. Jak vidíte, syntaxe je podobná běžnému indexu, jen jsme museli předat informace o typu indexu, protože je výchozí B+Strom. Potom jsme byli připraveni spustit nějaké dotazy.

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Starship');
+-----------+----------+------------------------------------+
| c1        | c2       | c3                                 |
+-----------+----------+------------------------------------+
| 119794610 | 12007923 | Starship Troopers 3                |
| 250627749 | 12479782 | Miranda class starship (Star Trek) |
| 250971304 | 12481409 | Starship Hospital                  |
| 253430758 | 12489743 | Starship Children's Hospital       |
+-----------+----------+------------------------------------+
4 rows in set (0.009 sec)

Jak můžete vidět, syntaxe SELECT je mírně odlišná, než na co jsme zvyklí. Pro fulltextové vyhledávání byste měli použít syntaxi MATCH() … AGAINST (), kde v MATCH() předáte sloupec nebo sloupce, které chcete hledat, a v AGAINST() předáte seznam klíčových slov oddělených čárkami. Z výstupu můžete vidět, že ve výchozím nastavení vyhledávání nerozlišuje malá a velká písmena a prohledává celý řetězec, ne jen začátek, jak je tomu u indexů B+Tree. Porovnejme, jak by to vypadalo, kdybychom na sloupec ‚c3‘ přidali normální index - FULLTEXT a indexy B+Tree mohou bez problémů koexistovat na stejném sloupci. O tom, který bude použit, se rozhodne na základě syntaxe SELECT.

MariaDB [ft_data]> ALTER TABLE ft_data.ft_table ADD INDEX idx_c3 (c3);
Query OK, 0 rows affected (1.884 sec)
Records: 0  Duplicates: 0  Warnings: 0

Po vytvoření indexu se podívejme na výstup vyhledávání:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE c3 LIKE 'Starship%';
+-----------+----------+------------------------------+
| c1        | c2       | c3                           |
+-----------+----------+------------------------------+
| 253430758 | 12489743 | Starship Children's Hospital |
| 250971304 | 12481409 | Starship Hospital            |
| 119794610 | 12007923 | Starship Troopers 3          |
+-----------+----------+------------------------------+
3 rows in set (0.001 sec)

Jak vidíte, náš dotaz vrátil pouze tři řádky. To se očekává, protože hledáme řádky, které začínají pouze řetězcem ‚Starship‘.

MariaDB [ft_data]> EXPLAIN SELECT * FROM ft_data.ft_table WHERE c3 LIKE 'Starship%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ft_table
         type: range
possible_keys: idx_c3,idx_ft
          key: idx_c3
      key_len: 103
          ref: NULL
         rows: 3
        Extra: Using where; Using index
1 row in set (0.000 sec)

Když zkontrolujeme výstup EXPLAIN, můžeme vidět, že index byl použit k vyhledání dat. Ale co když chceme hledat všechny řádky, které obsahují řetězec ‚Starship‘, bez ohledu na to, jestli je na začátku nebo ne. Musíme napsat následující dotaz:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE c3 LIKE '%Starship%';
+-----------+----------+------------------------------------+
| c1        | c2       | c3                                 |
+-----------+----------+------------------------------------+
| 250627749 | 12479782 | Miranda class starship (Star Trek) |
| 253430758 | 12489743 | Starship Children's Hospital       |
| 250971304 | 12481409 | Starship Hospital                  |
| 119794610 | 12007923 | Starship Troopers 3                |
+-----------+----------+------------------------------------+
4 rows in set (0.084 sec)

Výstup odpovídá tomu, co jsme získali z fulltextového vyhledávání.

MariaDB [ft_data]> EXPLAIN SELECT * FROM ft_data.ft_table WHERE c3 LIKE '%Starship%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ft_table
         type: index
possible_keys: NULL
          key: idx_c3
      key_len: 103
          ref: NULL
         rows: 473367
        Extra: Using where; Using index
1 row in set (0.000 sec)

EXPLAIN je však jiný - jak můžete vidět, stále používá index, ale tentokrát provádí úplné skenování indexu. To je možné, protože jsme indexovali celý sloupec c3, takže všechna data jsou dostupná v indexu. Skenování indexu bude mít za následek náhodné čtení z tabulky, ale pro tak malou tabulku se MariaDB rozhodla, že je to efektivnější než čtení celé tabulky. Vezměte prosím na vědomí dobu provedení:0,084 s pro náš běžný SELECT. V porovnání s fulltextovým dotazem je to špatné:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Starship');
+-----------+----------+------------------------------------+
| c1        | c2       | c3                                 |
+-----------+----------+------------------------------------+
| 119794610 | 12007923 | Starship Troopers 3                |
| 250627749 | 12479782 | Miranda class starship (Star Trek) |
| 250971304 | 12481409 | Starship Hospital                  |
| 253430758 | 12489743 | Starship Children's Hospital       |
+-----------+----------+------------------------------------+
4 rows in set (0.001 sec)

Jak můžete vidět, spuštění dotazu, který používá FULLTEXT index, trvalo 0,001 s. Hovoříme zde o řádových rozdílech.

MariaDB [ft_data]> EXPLAIN SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Starship')\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ft_table
         type: fulltext
possible_keys: idx_ft
          key: idx_ft
      key_len: 0
          ref:
         rows: 1
        Extra: Using where
1 row in set (0.000 sec)

Zde je návod, jak vypadá výstup EXPLAIN pro dotaz pomocí FULLTEXT indexu – tato skutečnost je označena typem:fulltext.

Fulltextové dotazy mají také některé další funkce. Je například možné vrátit řádky, které mohou být relevantní pro hledaný výraz. MariaDB hledá slova umístěná poblíž řádku, který hledáte, a poté je spusťte také pro vyhledávání.

MariaDB [(none)]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Starship');
+-----------+----------+------------------------------------+
| c1        | c2       | c3                                 |
+-----------+----------+------------------------------------+
| 119794610 | 12007923 | Starship Troopers 3                |
| 250627749 | 12479782 | Miranda class starship (Star Trek) |
| 250971304 | 12481409 | Starship Hospital                  |
| 253430758 | 12489743 | Starship Children's Hospital       |
+-----------+----------+------------------------------------+
4 rows in set (0.001 sec)

V našem případě slovo 'Starship' může souviset se slovy jako 'Troopers', 'class', 'Star Trek', 'Hospital' atd. Pro použití této funkce bychom měli spustit dotaz s modifikátorem "WITH QUERY EXPANSION":

MariaDB [(none)]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Starship' WITH QUERY EXPANSION) LIMIT 10;
+-----------+----------+-------------------------------------+
| c1        | c2       | c3                                  |
+-----------+----------+-------------------------------------+
| 250627749 | 12479782 | Miranda class starship (Star Trek)  |
| 119794610 | 12007923 | Starship Troopers 3                 |
| 253430758 | 12489743 | Starship Children's Hospital        |
| 250971304 | 12481409 | Starship Hospital                   |
| 277700214 | 12573467 | Star ship troopers                  |
|  86748633 | 11886457 | Troopers Drum and Bugle Corps       |
| 255120817 | 12495666 | Casper Troopers                     |
| 396408580 | 13014545 | Battle Android Troopers             |
|  12453401 | 11585248 | Star trek tos                       |
|  21380240 | 11622781 | Who Mourns for Adonais? (Star Trek) |
+-----------+----------+-------------------------------------+
10 rows in set (0.002 sec)

Výstup obsahoval velký počet řádků, ale tato ukázka je dostatečná, abyste viděli, jak to funguje. Dotaz vrátil řádky jako:

„Troopers Drum and Bugle Corps“

„Battle Android Troopers“

Ty jsou založeny na hledání slova „Troopers“. Také vrátil řádky s řetězci jako:

„Star trek tos“

„Kdo truchlí pro Adonaise? (Star Trek)“

Které jsou samozřejmě založeny na vyhledávání slova ‚Start Trek‘.

Pokud byste potřebovali větší kontrolu nad výrazem, který chcete vyhledat, můžete použít „IN BOOLEAN MODE“. Umožňuje použití dalších operátorů. Úplný seznam je v dokumentaci, my ukážeme jen pár příkladů.

Řekněme, že chceme hledat nejen slovo „Hvězda“, ale také další slova, která začínají řetězcem „Hvězda“:

MariaDB [(none)]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('Star*' IN BOOLEAN MODE) LIMIT 10;
+----------+----------+---------------------------------------------------+
| c1       | c2       | c3                                                |
+----------+----------+---------------------------------------------------+
| 20014704 | 11614055 | Ringo Starr and His third All-Starr Band-Volume 1 |
|   154810 | 11539775 | Rough blazing star                                |
|   154810 | 11539787 | Great blazing star                                |
|   234851 | 11540119 | Mary Star of the Sea High School                  |
|   325782 | 11540427 | HMS Starfish (19S)                                |
|   598616 | 11541589 | Dwarf (star)                                      |
|  1951655 | 11545092 | Yellow starthistle                                |
|  2963775 | 11548654 | Hydrogenated starch hydrolysates                  |
|  3248823 | 11549445 | Starbooty                                         |
|  3993625 | 11553042 | Harvest of Stars                                  |
+----------+----------+---------------------------------------------------+
10 rows in set (0.001 sec)

Jak můžete vidět, ve výstupu máme řádky, které obsahují řetězce jako ‚Hvězdy‘, ‚Hvězdice‘ nebo ‚škrob‘.

Další případ použití pro režim BOOLEAN. Řekněme, že chceme vyhledat řádky, které jsou relevantní pro Sněmovnu reprezentantů v Pensylvánii. Pokud spustíme běžný dotaz, dostaneme výsledky nějak související s kterýmkoli z těchto řetězců:

MariaDB [ft_data]> SELECT COUNT(*) FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('House, Representatives, Pennsylvania');
+----------+
| COUNT(*) |
+----------+
|     1529 |
+----------+
1 row in set (0.005 sec)
MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('House, Representatives, Pennsylvania') LIMIT 20;
+-----------+----------+--------------------------------------------------------------------------+
| c1        | c2       | c3                                                                       |
+-----------+----------+--------------------------------------------------------------------------+
| 198783294 | 12289308 | Pennsylvania House of Representatives, District 175                      |
| 236302417 | 12427322 | Pennsylvania House of Representatives, District 156                      |
| 236373831 | 12427423 | Pennsylvania House of Representatives, District 158                      |
| 282031847 | 12588702 | Pennsylvania House of Representatives, District 47                       |
| 282031847 | 12588772 | Pennsylvania House of Representatives, District 196                      |
| 282031847 | 12588864 | Pennsylvania House of Representatives, District 92                       |
| 282031847 | 12588900 | Pennsylvania House of Representatives, District 93                       |
| 282031847 | 12588904 | Pennsylvania House of Representatives, District 94                       |
| 282031847 | 12588909 | Pennsylvania House of Representatives, District 193                      |
| 303827502 | 12671054 | Pennsylvania House of Representatives, District 55                       |
| 303827502 | 12671089 | Pennsylvania House of Representatives, District 64                       |
| 337545922 | 12797838 | Pennsylvania House of Representatives, District 95                       |
| 219202000 | 12366957 | United States House of Representatives House Resolution 121              |
| 277521229 | 12572732 | United States House of Representatives proposed House Resolution 121     |
|  20923615 | 11618759 | Special elections to the United States House of Representatives          |
|  20923615 | 11618772 | List of Special elections to the United States House of Representatives  |
|  37794558 | 11693157 | Nebraska House of Representatives                                        |
|  39430531 | 11699551 | Belgian House of Representatives                                         |
|  53779065 | 11756435 | List of United States House of Representatives elections in North Dakota |
|  54048114 | 11757334 | 2008 United States House of Representatives election in North Dakota     |
+-----------+----------+--------------------------------------------------------------------------+
20 rows in set (0.003 sec)

Jak můžete vidět, našli jsme několik užitečných údajů, ale našli jsme také údaje, které nejsou pro naše vyhledávání zcela relevantní. Naštěstí můžeme takový dotaz upřesnit:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('+House, +Representatives, +Pennsylvania' IN BOOLEAN MODE);
+-----------+----------+-----------------------------------------------------+
| c1        | c2       | c3                                                  |
+-----------+----------+-----------------------------------------------------+
| 198783294 | 12289308 | Pennsylvania House of Representatives, District 175 |
| 236302417 | 12427322 | Pennsylvania House of Representatives, District 156 |
| 236373831 | 12427423 | Pennsylvania House of Representatives, District 158 |
| 282031847 | 12588702 | Pennsylvania House of Representatives, District 47  |
| 282031847 | 12588772 | Pennsylvania House of Representatives, District 196 |
| 282031847 | 12588864 | Pennsylvania House of Representatives, District 92  |
| 282031847 | 12588900 | Pennsylvania House of Representatives, District 93  |
| 282031847 | 12588904 | Pennsylvania House of Representatives, District 94  |
| 282031847 | 12588909 | Pennsylvania House of Representatives, District 193 |
| 303827502 | 12671054 | Pennsylvania House of Representatives, District 55  |
| 303827502 | 12671089 | Pennsylvania House of Representatives, District 64  |
| 337545922 | 12797838 | Pennsylvania House of Representatives, District 95  |
+-----------+----------+-----------------------------------------------------+
12 rows in set (0.001 sec)

Jak vidíte, přidáním operátoru „+“ jsme dali jasně najevo, že nás zajímá pouze výstup, kde dané slovo existuje. V důsledku toho jsou data, která jsme dostali jako odpověď, přesně to, co jsme hledali.

Slova můžeme z vyhledávání také vyloučit. Řekněme, že hledáme létající věci, ale naše výsledky hledání jsou kontaminovány různými létajícími zvířaty, která nás nezajímají. Lišek, veverek a žab se můžeme snadno zbavit:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('+flying -fox* -squirrel* -frog*' IN BOOLEAN MODE) LIMIT 10;
+----------+----------+-----------------------------------------------------+
| c1       | c2       | c3                                                  |
+----------+----------+-----------------------------------------------------+
| 13340153 | 11587884 | List of surviving Boeing B-17 Flying Fortresses     |
| 16774061 | 11600031 | Flying Dutchman Funicular                           |
| 23137426 | 11631421 | 80th Flying Training Wing                           |
| 26477490 | 11646247 | Kites and Kite Flying                               |
| 28568750 | 11655638 | Fear of Flying                                      |
| 28752660 | 11656721 | Flying Machine (song)                               |
| 31375047 | 11666654 | Flying Dutchman (train)                             |
| 32726276 | 11672784 | Flying Wazuma                                       |
| 47115925 | 11728593 | The Flying Locked Room! Kudou Shinichi's First Case |
| 64330511 | 11796326 | The Church of the Flying Spaghetti Monster          |
+----------+----------+-----------------------------------------------------+
10 rows in set (0.001 sec)

Poslední funkcí, kterou bychom rádi ukázali, je schopnost vyhledávat přesnou nabídku:

MariaDB [ft_data]> SELECT * FROM ft_data.ft_table WHERE MATCH(c3) AGAINST ('"People\'s Republic of China"' IN BOOLEAN MODE) LIMIT 10;
+-----------+----------+------------------------------------------------------------------------------------------------------+
| c1        | c2       | c3                                                                                                   |
+-----------+----------+------------------------------------------------------------------------------------------------------+
|  12093896 | 11583713 | Religion in the People's Republic of China                                                           |
|  25280224 | 11640533 | Political rankings in the People's Republic of China                                                 |
|  43930887 | 11716084 | Cuisine of the People's Republic of China                                                            |
|  62272294 | 11789886 | Office of the Commissioner of the Ministry of Foreign Affairs of the People's Republic of China in t |
|  70970904 | 11824702 | Scouting in the People's Republic of China                                                           |
| 154301063 | 12145003 | Tibetan culture under the People's Republic of China                                                 |
| 167640800 | 12189851 | Product safety in the People's Republic of China                                                     |
| 172735782 | 12208560 | Agriculture in the people's republic of china                                                        |
| 176185516 | 12221117 | Special Economic Zone of the People's Republic of China                                              |
| 197034766 | 12282071 | People's Republic of China and the United Nations                                                    |
+-----------+----------+------------------------------------------------------------------------------------------------------+
10 rows in set (0.001 sec)

Jak vidíte, fulltextové vyhledávání v MariaDB funguje docela dobře, je také rychlejší a flexibilnější než vyhledávání pomocí indexů B+Tree. Mějte však na paměti, že se v žádném případě nejedná o způsob nakládání s velkými objemy dat – s nárůstem dat se proveditelnost tohoto řešení snižuje. Pro malé soubory dat je toto řešení dokonale platné. Rozhodně vám to může získat více času na implementaci specializovaných řešení fulltextového vyhledávání, jako je Sphinx nebo Lucene. Všechny funkce, které jsme popsali, jsou samozřejmě dostupné v clusterech MariaDB nasazených z ClusterControl.


  1. SQL Server:DELETE vs TRUNCATE

  2. Jak získat poslední vložené ID?

  3. Jaká je při provádění uložené procedury výhoda použití CommandType.StoredProcedure oproti použití CommandType.Text?

  4. Vysvětlení příkazu DROP TABLE na serveru SQL