MariaDB
 sql >> Teknologi Basis Data >  >> RDS >> MariaDB

Pengantar Pencarian Teks Lengkap di MariaDB

Basis data dimaksudkan untuk menyimpan dan meminta data secara efisien. Masalahnya, ada banyak jenis data yang bisa kita simpan:angka, string, JSON, data geometris. Database menggunakan metode yang berbeda untuk menyimpan berbagai jenis data - struktur tabel, indeks. Tidak selalu cara yang sama untuk menyimpan dan menanyakan data adalah efisien untuk semua jenisnya, sehingga cukup sulit untuk menggunakan solusi satu-untuk-semua. Akibatnya, database mencoba menggunakan pendekatan yang berbeda untuk tipe data yang berbeda. Misalnya, di MySQL atau MariaDB kami memiliki solusi generik yang berkinerja baik seperti InnoDB, yang berfungsi dengan baik di sebagian besar kasus, tetapi kami juga memiliki fungsi terpisah untuk bekerja dengan data JSON, indeks spasial terpisah untuk mempercepat kueri data geometris atau indeks teks lengkap , membantu dengan data teks. Di blog ini, kita akan melihat bagaimana MariaDB dapat digunakan untuk bekerja dengan data teks lengkap.

Indeks B+Tree reguler di InnoDB juga dapat digunakan untuk mempercepat pencarian data teks. Masalah utamanya adalah, karena struktur dan sifatnya, mereka hanya dapat membantu mencari awalan paling kiri. Juga mahal untuk mengindeks teks dalam jumlah besar (yang, mengingat keterbatasan awalan paling kiri, tidak terlalu masuk akal). Mengapa? Mari kita lihat contoh sederhana. Kami memiliki kalimat berikut:

“Rubah coklat cepat melompati anjing malas”

Menggunakan indeks reguler di InnoDB, kita dapat mengindeks kalimat lengkap:

“Rubah coklat cepat melompati anjing malas”

Intinya, ketika mencari data ini, kita harus mencari awalan paling kiri penuh. Jadi kueri seperti:

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

Akan mendapat manfaat dari indeks ini tetapi kueri seperti:

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

Tidak akan. Tidak ada entri dalam indeks yang dimulai dari 'cepat'. Ada entri dalam indeks yang berisi 'quick' tetapi dimulai dari 'The', sehingga tidak dapat digunakan. Akibatnya, hampir tidak mungkin untuk secara efisien meminta data teks menggunakan indeks B+Tree. Untungnya, baik MyISAM dan InnoDB telah mengimplementasikan indeks FULLTEXT, yang dapat digunakan untuk benar-benar bekerja dengan data teks di MariaDB. Sintaksnya sedikit berbeda dibandingkan dengan SELECT biasa, mari kita lihat apa yang bisa kita lakukan dengannya. Adapun data kami menggunakan file indeks acak dari dump database Wikipedia. Struktur datanya seperti di bawah ini:

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

Hasilnya, kami membuat tabel dengan dua kolom BIG INT dan satu VARCHAR.

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

Setelah itu kami memuat 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

Kami juga membuat indeks FULLTEXT. Seperti yang Anda lihat, sintaks untuk itu mirip dengan indeks biasa, kami hanya perlu meneruskan informasi tentang tipe indeks karena defaultnya adalah B+Tree. Kemudian kami siap menjalankan beberapa kueri.

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)

Seperti yang Anda lihat, sintaks untuk SELECT sedikit berbeda dari yang biasa kita gunakan. Untuk pencarian teks lengkap Anda harus menggunakan sintaks MATCH() … AGAINST(), di mana dalam MATCH() Anda melewati kolom atau kolom yang ingin Anda cari dan dalam AGAINST() Anda melewati daftar kata kunci yang dipisahkan koma. Anda dapat melihat dari output bahwa secara default pencarian tidak peka huruf besar/kecil dan pencarian seluruh string, bukan hanya awal seperti dengan indeks B+Tree. Mari kita bandingkan bagaimana tampilannya jika kita menambahkan indeks normal pada kolom 'c3' - indeks FULLTEXT dan B+Tree dapat hidup berdampingan di kolom yang sama tanpa masalah. Yang akan digunakan ditentukan berdasarkan sintaks 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

Setelah indeks dibuat, mari kita lihat hasil pencarian:

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)

Seperti yang Anda lihat, kueri kami hanya mengembalikan tiga baris. Ini diharapkan karena kami mencari baris yang hanya dimulai dengan string '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)

Ketika kita memeriksa output EXPLAIN kita dapat melihat bahwa indeks telah digunakan untuk mencari data. Tetapi bagaimana jika kita ingin mencari semua baris yang berisi string 'Starship', tidak peduli apakah itu di awal atau tidak. Kita harus menulis query berikut:

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)

Outputnya cocok dengan apa yang kami dapatkan dari pencarian teks lengkap.

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 berbeda - seperti yang Anda lihat masih menggunakan indeks tetapi kali ini melakukan pemindaian indeks penuh. Itu dimungkinkan karena kami mengindeks kolom c3 penuh sehingga semua data tersedia di index. Pemindaian indeks akan menghasilkan pembacaan acak dari tabel tetapi untuk tabel kecil seperti itu, MariaDB memutuskannya lebih efisien daripada membaca seluruh tabel. Harap perhatikan waktu eksekusi:0,084 detik untuk SELECT reguler kami. Membandingkan ini dengan kueri teks lengkap, ini buruk:

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)

Seperti yang Anda lihat, kueri yang menggunakan indeks FULLTEXT membutuhkan waktu 0,001 detik untuk dieksekusi. Di sini kita berbicara tentang perbedaan orde besarnya.

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)

Berikut adalah tampilan keluaran EXPLAIN untuk kueri yang menggunakan indeks FULLTEXT - fakta tersebut ditunjukkan oleh jenis:fulltext.

Kueri teks lengkap juga memiliki beberapa fitur lain. Dimungkinkan, misalnya, untuk mengembalikan baris yang mungkin relevan dengan istilah pencarian. MariaDB mencari kata-kata yang terletak di dekat baris yang Anda cari dan kemudian menjalankan pencarian juga untuk kata-kata tersebut.

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)

Dalam kasus kami, kata 'Starship' dapat dikaitkan dengan kata-kata seperti 'Troopers', 'class', 'Star Trek', 'Hospital', dll. Untuk menggunakan fitur ini, kami harus menjalankan kueri dengan pengubah “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)

Outputnya berisi sejumlah besar baris tetapi sampel ini cukup untuk melihat cara kerjanya. Kueri mengembalikan baris seperti:

“Gendang Pasukan dan Korps Terompet”

“Pertempuran Pasukan Android”

Itu didasarkan pada pencarian kata 'Troopers'. Itu juga mengembalikan baris dengan string seperti:

“Start trek tos”

“Siapa yang Berduka untuk Adonais? (Star Trek)”

Yang, jelas, didasarkan pada pencarian kata 'Start Trek'.

Jika Anda membutuhkan lebih banyak kontrol atas istilah yang ingin Anda cari, Anda dapat menggunakan "IN BOOLEAN MODE". Hal ini memungkinkan untuk menggunakan operator tambahan. Daftar lengkapnya ada di dokumentasi, kami akan menunjukkan beberapa contoh saja.

Katakanlah kita ingin mencari tidak hanya kata 'Bintang' tetapi juga kata lain yang dimulai dengan string 'Bintang':

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)

Seperti yang Anda lihat, dalam output kami memiliki baris yang berisi string seperti 'Bintang', 'Bintang Laut' atau 'pati'.

Kasus penggunaan lain untuk mode BOOLEAN. Katakanlah kita ingin mencari baris yang relevan dengan House of Representatives di Pennsylvania. Jika kita menjalankan kueri biasa, kita akan mendapatkan hasil yang terkait dengan salah satu string tersebut:

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)

Seperti yang Anda lihat, kami menemukan beberapa data yang berguna tetapi kami juga menemukan data yang sama sekali tidak relevan dengan pencarian kami. Untungnya, kami dapat memperbaiki kueri seperti itu:

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)

Seperti yang Anda lihat, dengan menambahkan operator '+' kami memperjelas bahwa kami hanya tertarik pada output di mana kata yang diberikan ada. Hasilnya, data yang kami dapatkan sebagai tanggapan persis seperti yang kami cari.

Kami juga dapat mengecualikan kata-kata dari pencarian. Katakanlah kita mencari benda terbang tetapi hasil pencarian kita terkontaminasi oleh berbagai hewan terbang yang tidak kita minati. Kita dapat dengan mudah menyingkirkan rubah, tupai, dan katak:

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)

Fitur terakhir yang ingin kami tunjukkan adalah kemampuan untuk mencari kutipan yang tepat:

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)

Seperti yang Anda lihat, pencarian teks lengkap di MariaDB bekerja cukup baik, juga lebih cepat dan lebih fleksibel daripada pencarian menggunakan indeks B+Tree. Harap diingat bahwa ini sama sekali bukan cara menangani volume data yang besar - dengan pertumbuhan data, kelayakan solusi ini akan berkurang. Namun, untuk kumpulan data kecil, solusi ini benar-benar valid. Ini pasti dapat memberi Anda lebih banyak waktu untuk, pada akhirnya, menerapkan solusi pencarian teks lengkap khusus seperti Sphinx atau Lucene. Tentu saja, semua fitur yang kami jelaskan tersedia di klaster MariaDB yang diterapkan dari ClusterControl.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Enkripsi Basis Data:Mengapa dan Di Mana Anda Perlu Memiliki Enkripsi Data

  2. Bagaimana CONVERT_TZ() Bekerja di MariaDB

  3. MariaDB JSON_QUOTE() Dijelaskan

  4. Menjalankan Kueri Analisis Data Besar Menggunakan SQL dan Presto

  5. Membandingkan Server MariaDB dengan MariaDB Cluster