sql >> Database >  >> RDS >> MariaDB

Een inleiding tot zoeken in volledige tekst in MariaDB

Databases zijn bedoeld om gegevens efficiënt op te slaan en op te vragen. Het probleem is dat er veel verschillende soorten gegevens zijn die we kunnen opslaan:getallen, strings, JSON, geometrische gegevens. Databases gebruiken verschillende methoden om verschillende soorten gegevens op te slaan:tabelstructuur, indexen. Niet altijd dezelfde manier om de gegevens op te slaan en op te vragen is efficiënt voor alle typen, waardoor het vrij moeilijk is om een ​​pasklare oplossing te gebruiken. Als gevolg hiervan proberen databases verschillende benaderingen te gebruiken voor verschillende gegevenstypen. In MySQL of MariaDB hebben we bijvoorbeeld een generieke, goed presterende oplossing zoals InnoDB, die in de meeste gevallen prima werkt, maar we hebben ook afzonderlijke functies om met JSON-gegevens te werken, afzonderlijke ruimtelijke indexen om het opvragen van geometrische gegevens of volledige tekstindexen te versnellen , helpen met tekstgegevens. In deze blog bekijken we hoe MariaDB kan worden gebruikt om met full-text data te werken.

Reguliere B+Tree-indexen in InnoDB kunnen ook worden gebruikt om het zoeken naar tekstgegevens te versnellen. Het belangrijkste probleem is dat ze vanwege hun structuur en aard alleen kunnen helpen bij het zoeken naar de meest linkse voorvoegsels. Het is ook duur om grote hoeveelheden tekst te indexeren (wat, gezien de beperkingen van het meest linkse voorvoegsel, niet echt logisch is). Waarom? Laten we een eenvoudig voorbeeld bekijken. We hebben de volgende zin:

"De snelle bruine vos springt over de luie hond"

Met behulp van reguliere indexen in InnoDB kunnen we de volledige zin indexeren:

"De snelle bruine vos springt over de luie hond"

Het punt is dat we bij het zoeken naar deze gegevens het volledige voorvoegsel uiterst links moeten opzoeken. Dus een zoekopdracht als:

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

Zal profiteren van deze index, maar een zoekopdracht als:

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

Zal niet. Er is geen vermelding in de index die begint met 'snel'. Er is een vermelding in de index die 'snel' bevat maar begint met 'De', dus deze kan niet worden gebruikt. Als gevolg hiervan is het vrijwel onmogelijk om efficiënt tekstgegevens op te vragen met behulp van B+Tree-indexen. Gelukkig hebben zowel MyISAM als InnoDB FULLTEXT-indexen geïmplementeerd, die kunnen worden gebruikt om daadwerkelijk met tekstgegevens op MariaDB te werken. De syntaxis is iets anders dan bij gewone SELECT's, laten we eens kijken wat we ermee kunnen doen. Wat betreft gegevens gebruikten we een willekeurig indexbestand uit de dump van de Wikipedia-database. De gegevensstructuur is als volgt:

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

Als resultaat hebben we een tabel gemaakt met twee BIG INT-kolommen en één VARCHAR.

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

Daarna hebben we de gegevens geladen:

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

We hebben ook de FULLTEXT-index gemaakt. Zoals je kunt zien, is de syntaxis daarvoor vergelijkbaar met de reguliere index, we moesten alleen de informatie over het indextype doorgeven omdat dit standaard is ingesteld op B+Tree. Toen waren we klaar om enkele zoekopdrachten uit te voeren.

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)

Zoals je kunt zien, is de syntaxis voor de SELECT iets anders dan we gewend zijn. Voor zoeken in volledige tekst moet u de syntaxis MATCH() ... AGAINST () gebruiken, waarbij u in MATCH() de kolom of kolommen doorgeeft die u wilt doorzoeken en in AGAINST() een door komma's gescheiden lijst met trefwoorden doorgeeft. U kunt aan de uitvoer zien dat zoeken standaard hoofdletterongevoelig is en dat het de hele reeks doorzoekt, niet alleen het begin zoals bij B+Tree-indexen. Laten we eens vergelijken hoe het eruit zal zien als we een normale index zouden toevoegen aan de 'c3'-kolom - FULLTEXT- en B+Tree-indexen kunnen zonder problemen naast elkaar in dezelfde kolom bestaan. Welke wordt gebruikt, wordt bepaald op basis van de SELECT-syntaxis.

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

Laten we, nadat de index is aangemaakt, eens kijken naar de zoekresultaten:

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)

Zoals u kunt zien, heeft onze zoekopdracht slechts drie rijen geretourneerd. Dit wordt verwacht omdat we op zoek zijn naar rijen die alleen beginnen met een tekenreeks '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)

Wanneer we de EXPLAIN-uitvoer controleren, kunnen we zien dat de index is gebruikt om de gegevens op te zoeken. Maar wat als we willen zoeken naar alle rijen die de tekenreeks 'Starship' bevatten, ongeacht of deze aan het begin staat of niet. We moeten de volgende vraag schrijven:

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)

De uitvoer komt overeen met wat we hebben gekregen van de fulltext-zoekopdracht.

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)

De EXPLAIN is echter anders - zoals je kunt zien gebruikt het nog steeds de index, maar deze keer voert het een volledige indexscan uit. Dat is mogelijk omdat we de volledige c3-kolom hebben geïndexeerd, zodat alle gegevens beschikbaar zijn in de index. Indexscan zal resulteren in willekeurige uitlezingen van de tabel, maar voor zo'n kleine tabel besloot MariaDB dat het efficiënter was dan het lezen van de hele tabel. Let op de uitvoeringstijd:0.084s voor onze reguliere SELECT. Als je dit vergelijkt met een volledige tekstzoekopdracht, is het slecht:

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)

Zoals u kunt zien, kostte het uitvoeren van query's die de FULLTEXT-index gebruiken 0,001 seconden. We hebben het hier over orden van grootteverschillen.

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)

Hier ziet u hoe de EXPLAIN-uitvoer eruitziet voor de zoekopdracht met de FULLTEXT-index - dat feit wordt aangegeven door het type:volledige tekst.

Fulltext-query's hebben ook enkele andere functies. Het is bijvoorbeeld mogelijk om rijen terug te geven die mogelijk relevant zijn voor de zoekterm. MariaDB zoekt naar woorden in de buurt van de rij waarnaar u zoekt en voert vervolgens ook een zoekopdracht naar hen uit.

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)

In ons geval kan het woord 'Starship' gerelateerd zijn aan woorden als 'Troopers', 'class', 'Star Trek', 'Hospital' enz. Om deze functie te gebruiken, moeten we de zoekopdracht uitvoeren met de modificatie "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)

De uitvoer bevatte een groot aantal rijen, maar dit voorbeeld is voldoende om te zien hoe het werkt. De zoekopdracht leverde rijen op zoals:

"Troopers Drum and Bugle Corps"

"Vecht tegen Android Troopers"

Die zijn gebaseerd op de zoekopdracht naar het woord ‘Troopers’. Het retourneerde ook rijen met strings zoals:

"Star trek tos"

“Wie rouwt om Adonais? (Star Trek)”

Die uiteraard zijn gebaseerd op de zoekopdracht voor het woord 'Start Trek'.

Als u meer controle wilt hebben over de term waarnaar u wilt zoeken, kunt u "IN BOOLEAN-MODUS" gebruiken. Het maakt het mogelijk om extra operators te gebruiken. De volledige lijst staat in de documentatie, we zullen slechts een paar voorbeelden laten zien.

Laten we zeggen dat we niet alleen naar het woord 'Ster' willen zoeken, maar ook naar andere woorden die beginnen met de tekenreeks 'Ster':

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)

Zoals je kunt zien, hebben we in de uitvoer rijen die strings bevatten zoals 'Stars', 'Starfish' of 'starch'.

Nog een use-case voor de BOOLEAN-modus. Laten we zeggen dat we willen zoeken naar rijen die relevant zijn voor het Huis van Afgevaardigden in Pennsylvania. Als we een normale zoekopdracht uitvoeren, krijgen we resultaten die op de een of andere manier gerelateerd zijn aan een van deze strings:

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)

Zoals u kunt zien, hebben we enkele nuttige gegevens gevonden, maar we hebben ook gegevens gevonden die totaal niet relevant zijn voor onze zoekopdracht. Gelukkig kunnen we zo'n zoekopdracht verfijnen:

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)

Zoals je kunt zien, hebben we door de operator '+' toe te voegen duidelijk gemaakt dat we alleen geïnteresseerd zijn in de uitvoer waar het opgegeven woord bestaat. Als gevolg hiervan zijn de gegevens die we als reactie kregen precies wat we zochten.

We kunnen ook woorden uitsluiten van de zoekopdracht. Laten we zeggen dat we op zoek zijn naar vliegende dingen, maar onze zoekresultaten zijn besmet door verschillende vliegende dieren waarin we niet geïnteresseerd zijn. We kunnen gemakkelijk van vossen, eekhoorns en kikkers afkomen:

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)

Het laatste kenmerk dat we willen laten zien, is de mogelijkheid om naar de exacte offerte te zoeken:

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)

Zoals u kunt zien, werkt fulltext zoeken in MariaDB vrij goed, het is ook sneller en flexibeler dan zoeken met B+Tree-indexen. Houd er echter rekening mee dat dit geenszins een manier is om grote hoeveelheden gegevens te verwerken - met de gegevensgroei zal de haalbaarheid van deze oplossing afnemen. Toch is deze oplossing voor de kleine datasets perfect geldig. Het kan u zeker meer tijd opleveren om uiteindelijk toegewijde full-text zoekoplossingen zoals Sphinx of Lucene te implementeren. Natuurlijk zijn alle functies die we hebben beschreven beschikbaar in MariaDB-clusters die zijn geïmplementeerd vanuit ClusterControl.


  1. Willekeurige rijen selecteren met MySQL

  2. MySQL ROUND() Functie – Rond een getal af op een bepaald aantal decimalen

  3. Geef een lijst met gehele getallen door van C# naar de opgeslagen procedure van Oracle

  4. Schending van integriteitsbeperking:1452 Kan een onderliggende rij niet toevoegen of bijwerken: