Cơ sở dữ liệu nhằm mục đích lưu trữ và truy vấn dữ liệu một cách hiệu quả. Vấn đề là, có nhiều loại dữ liệu khác nhau mà chúng ta có thể lưu trữ:số, chuỗi, JSON, dữ liệu hình học. Cơ sở dữ liệu sử dụng các phương pháp khác nhau để lưu trữ các loại dữ liệu khác nhau - cấu trúc bảng, chỉ mục. Không phải lúc nào cùng một cách lưu trữ và truy vấn dữ liệu cũng hiệu quả đối với tất cả các loại dữ liệu, điều này khiến cho việc sử dụng một giải pháp phù hợp cho tất cả trở nên khá khó khăn. Kết quả là, cơ sở dữ liệu cố gắng sử dụng các cách tiếp cận khác nhau cho các kiểu dữ liệu khác nhau. Ví dụ:trong MySQL hoặc MariaDB, chúng tôi có giải pháp chung, hoạt động tốt như InnoDB, hoạt động tốt trong hầu hết các trường hợp, nhưng chúng tôi cũng có các chức năng riêng biệt để làm việc với dữ liệu JSON, các chỉ mục không gian riêng biệt để tăng tốc độ truy vấn dữ liệu hình học hoặc chỉ mục văn bản đầy đủ , trợ giúp với dữ liệu văn bản. Trong blog này, chúng ta sẽ xem xét cách MariaDB có thể được sử dụng để làm việc với dữ liệu toàn văn.
Các chỉ mục B + Tree thông thường trong InnoDB cũng có thể được sử dụng để tăng tốc độ tìm kiếm dữ liệu văn bản. Vấn đề chính là, do cấu trúc và bản chất của chúng, chúng chỉ có thể giúp tìm kiếm các tiền tố ngoài cùng bên trái. Việc lập chỉ mục khối lượng lớn văn bản cũng rất tốn kém (do những hạn chế của tiền tố ngoài cùng bên trái, không thực sự có ý nghĩa). Tại sao? Hãy xem một ví dụ đơn giản. Chúng ta có câu sau:
“Con cáo nâu nhanh nhẹn nhảy qua con chó lười biếng”
Sử dụng các chỉ mục thông thường trong InnoDB, chúng ta có thể lập chỉ mục cho câu đầy đủ:
“Con cáo nâu nhanh nhẹn nhảy qua con chó lười biếng”
Vấn đề là, khi tìm kiếm dữ liệu này, chúng ta phải tra cứu đầy đủ tiền tố ngoài cùng bên trái. Vì vậy, một truy vấn như:
SELECT text FROM mytable WHERE sentence LIKE “The quick brown fox jumps”;
Sẽ được hưởng lợi từ chỉ mục này nhưng một truy vấn như:
SELECT text FROM mytable WHERE sentence LIKE “quick brown fox jumps”;
Sẽ không. Không có mục nhập nào trong chỉ mục bắt đầu từ "nhanh". Có một mục nhập trong chỉ mục có chứa "quick" nhưng bắt đầu từ "The", do đó không thể sử dụng mục này. Do đó, hầu như không thể truy vấn dữ liệu văn bản một cách hiệu quả bằng cách sử dụng các chỉ mục B + Tree. May mắn thay, cả MyISAM và InnoDB đều đã triển khai các chỉ mục FULLTEXT, có thể được sử dụng để thực sự làm việc với dữ liệu văn bản trên MariaDB. Cú pháp hơi khác so với các lệnh CHỌN thông thường, hãy cùng xem những gì chúng ta có thể làm với chúng. Đối với dữ liệu, chúng tôi sử dụng tệp chỉ mục ngẫu nhiên từ kết xuất của cơ sở dữ liệu Wikipedia. Cấu trúc dữ liệu như sau:
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
Kết quả là chúng tôi đã tạo bảng với hai cột INT LỚN và một VARCHAR.
MariaDB [(none)]> CREATE TABLE ft_data.ft_table (c1 BIGINT, c2 BIGINT, c3 VARCHAR, PRIMARY KEY (c1, c2);
Sau đó, chúng tôi đã tải dữ liệu:
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
Chúng tôi cũng đã tạo chỉ mục FULLTEXT. Như bạn có thể thấy, cú pháp của nó tương tự như chỉ mục thông thường, chúng tôi chỉ phải chuyển thông tin về loại chỉ mục vì nó được mặc định là B + Tree. Sau đó, chúng tôi đã sẵn sàng chạy một số truy vấn.
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)
Như bạn có thể thấy, cú pháp của SELECT hơi khác so với những gì chúng ta đã quen. Đối với tìm kiếm toàn văn bản, bạn nên sử dụng cú pháp MATCH ()… AGAINST (), trong đó MATCH () bạn chuyển cột hoặc các cột bạn muốn tìm kiếm và trong AGAINST (), bạn chuyển danh sách từ khóa được phân tách bằng dấu hôn mê. Bạn có thể thấy từ đầu ra rằng theo mặc định, tìm kiếm không phân biệt chữ hoa chữ thường và nó tìm kiếm toàn bộ chuỗi, không chỉ phần đầu như với các chỉ mục B + Tree. Hãy so sánh nó sẽ trông như thế nào nếu chúng ta thêm chỉ mục bình thường trên cột ‘c3’ - chỉ mục FULLTEXT và B + Tree có thể cùng tồn tại trên cùng một cột mà không gặp bất kỳ vấn đề gì. Cái nào sẽ được sử dụng được quyết định dựa trên cú pháp CHỌN.
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
Sau khi chỉ mục được tạo, hãy xem kết quả tìm kiếm:
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)
Như bạn có thể thấy, truy vấn của chúng tôi chỉ trả về ba hàng. Điều này được mong đợi vì chúng tôi đang tìm kiếm các hàng chỉ bắt đầu bằng chuỗi "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)
Khi chúng tôi kiểm tra đầu ra EXPLAIN, chúng tôi có thể thấy rằng chỉ mục đã được sử dụng để tra cứu dữ liệu. Nhưng điều gì sẽ xảy ra nếu chúng ta muốn tìm kiếm tất cả các hàng có chứa chuỗi ‘Starship’, bất kể nó có ở đầu hay không. Chúng ta phải viết câu truy vấn sau:
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)
Kết quả phù hợp với những gì chúng tôi nhận được từ tìm kiếm văn bản đầy đủ.
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)
Tuy nhiên, EXPLAIN lại khác - như bạn có thể thấy nó vẫn sử dụng chỉ mục nhưng lần này nó thực hiện quét toàn bộ chỉ mục. Điều đó có thể thực hiện được vì chúng tôi đã lập chỉ mục cột c3 đầy đủ để tất cả dữ liệu có sẵn trong chỉ mục. Việc quét chỉ mục sẽ dẫn đến việc đọc ngẫu nhiên từ bảng nhưng đối với bảng nhỏ như vậy, MariaDB quyết định nó hiệu quả hơn đọc toàn bộ bảng. Xin lưu ý thời gian thực hiện:0,084 giây đối với SELECT thông thường của chúng tôi. So sánh điều này với truy vấn toàn văn bản, thật tệ:
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)
Như bạn có thể thấy, truy vấn sử dụng chỉ mục FULLTEXT mất 0,001 giây để thực thi. Ở đây chúng ta đang nói về thứ tự chênh lệch độ lớn.
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)
Đây là cách đầu ra EXPLAIN trông như thế nào cho truy vấn sử dụng chỉ mục FULLTEXT - thực tế đó được chỉ ra bởi loại:fulltext.
Truy vấn toàn văn bản cũng có một số tính năng khác. Ví dụ:có thể trả về các hàng có thể liên quan đến cụm từ tìm kiếm. MariaDB tìm kiếm các từ nằm gần hàng mà bạn tìm kiếm và sau đó cũng chạy tìm kiếm chúng.
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)
Trong trường hợp của chúng tôi, từ 'Starship' có thể liên quan đến các từ như 'Troopers', 'class', 'Star Trek', 'Hospital', v.v. Để sử dụng tính năng này, chúng ta nên chạy truy vấn với trình sửa đổi "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)
Đầu ra chứa một số lượng lớn các hàng nhưng mẫu này đủ để xem nó hoạt động như thế nào. Truy vấn trả về các hàng như:
“Quân đoàn Drum và Bugle Corps”
“Battle Android Troopers”
Chúng dựa trên việc tìm kiếm từ 'Troopers'. Nó cũng trả về các hàng có chuỗi như:
“Star trek tos”
“Ai để tang cho Adonais? (Star Trek) ”
Rõ ràng, điều này dựa trên việc tra cứu từ "Start Trek".
Nếu bạn cần kiểm soát nhiều hơn đối với cụm từ bạn muốn tìm kiếm, bạn có thể sử dụng “TRONG CHẾ ĐỘ BOOLEAN”. Nó cho phép sử dụng các toán tử bổ sung. Danh sách đầy đủ có trong tài liệu, chúng tôi sẽ chỉ hiển thị một vài ví dụ.
Giả sử chúng tôi không chỉ muốn tìm kiếm từ 'Dấu sao' mà còn tìm kiếm các từ khác bắt đầu bằng chuỗi 'Dấu sao':
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)
Như bạn có thể thấy, trong đầu ra, chúng ta có các hàng chứa các chuỗi như "Stars", "Starfish" hoặc "tinh bột".
Một trường hợp sử dụng khác cho chế độ BOOLEAN. Giả sử chúng tôi muốn tìm kiếm các hàng có liên quan đến Hạ viện ở Pennsylvania. Nếu chúng tôi chạy truy vấn thông thường, chúng tôi sẽ nhận được kết quả bằng cách nào đó liên quan đến bất kỳ chuỗi nào trong số đó:
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)
Như bạn có thể thấy, chúng tôi đã tìm thấy một số dữ liệu hữu ích nhưng chúng tôi cũng tìm thấy dữ liệu hoàn toàn không liên quan đến tìm kiếm của chúng tôi. May mắn thay, chúng tôi có thể tinh chỉnh truy vấn như vậy:
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)
Như bạn có thể thấy, bằng cách thêm toán tử ‘+’, chúng tôi đã làm rõ rằng chúng tôi chỉ quan tâm đến đầu ra có từ đã cho. Do đó, dữ liệu chúng tôi nhận được phản hồi chính xác là những gì chúng tôi đang tìm kiếm.
Chúng tôi cũng có thể loại trừ các từ khỏi tìm kiếm. Giả sử chúng tôi đang tìm kiếm những thứ bay được nhưng kết quả tìm kiếm của chúng tôi bị ô nhiễm bởi các loài động vật bay khác nhau mà chúng tôi không quan tâm. Chúng tôi có thể dễ dàng loại bỏ cáo, sóc và ếch:
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)
Tính năng cuối cùng mà chúng tôi muốn hiển thị là khả năng tìm kiếm báo giá chính xác:
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)
Như bạn thấy, tìm kiếm toàn văn bản trong MariaDB hoạt động khá tốt, nó cũng nhanh hơn và linh hoạt hơn so với tìm kiếm bằng cách sử dụng các chỉ mục B + Tree. Xin lưu ý rằng đây không phải là cách xử lý khối lượng lớn dữ liệu - với tốc độ tăng trưởng dữ liệu, tính khả thi của giải pháp này sẽ giảm. Tuy nhiên, đối với các tập dữ liệu nhỏ, giải pháp này hoàn toàn hợp lệ. Nó chắc chắn có thể giúp bạn thêm thời gian để triển khai các giải pháp tìm kiếm toàn văn chuyên dụng như Sphinx hoặc Lucene. Tất nhiên, tất cả các tính năng mà chúng tôi đã mô tả đều có sẵn trong các cụm MariaDB được triển khai từ ClusterControl.