C, PHP, VB, .NET

Дневникът на Филип Петров


* Full-Text търсене с InnoDB в MySQL

Публикувано на 16 май 2012 в раздел Бази от Данни.

С наближаването на официалното излизане на стабилна версия 5.6 на MySQL идва време да поговорим и едно дългоочаквано нововъведение – full text search в InnoDB. Досега беше налично само за MyISAM таблици, но от предварителната версия за разработчици 5.6.4 вече може да се използва и от по-силния си събрат. Това беше и едно от малкото липсващи неща в InnoDB, което нерядко караше разработчиците да използват MyISAM.

Нека преди да започнем с примери да създадем тестова база от данни. Тя ще се състои от две таблици – потребители и публикации. Всеки потребител има уникален номер и име. В публикациите пазим уникален номер, номер на автор, заглавие, съдържание, дата на създаване и номер на статия, на която се отговаря (ако се публикува отговор на друго съобщение). Ще има два вида статии – едните ще са „главни“ (те ще имат попълнено заглавие, но няма да имат попълнена стойност за „отговор на“), а другите ще са „отговори“ (те ще имат попълнен „отговор на“, но няма да имат заглавие). Ето и въпросните две таблици:

CREATE DATABASE fts
  DEFAULT CHARACTER SET utf8
  DEFAULT COLLATE utf8_general_ci;

USE fts;

CREATE TABLE users(
  id SMALLINT UNSIGNED PRIMARY KEY,
  username VARCHAR(30) NOT NULL UNIQUE
)ENGINE=InnoDB;

CREATE TABLE posts(
  id INT UNSIGNED PRIMARY KEY,
  author SMALLINT UNSIGNED,
  FOREIGN KEY(author) REFERENCES users(id),
  title VARCHAR(255) NULL DEFAULT NULL,
  data TEXT NOT NULL,
  created DATETIME NOT NULL,
  reply_to INT UNSIGNED NULL DEFAULT NULL,
  FOREIGN KEY(reply_to) REFERENCES posts(id)
)ENGINE=InnoDB;

Нека създадем и един тригер, който ще се грижи за интегритета на данните:

DELIMITER //
CREATE TRIGGER post_integrity_check
BEFORE INSERT ON posts FOR EACH ROW
BEGIN
  DECLARE errormsg VARCHAR(255);
  DECLARE main_post_title VARCHAR(255);
  IF NEW.title IS NULL
  THEN
    IF NEW.reply_to IS NULL
    THEN
      SET errormsg = "Основните съобщения трябва да имат заглавие";
      SIGNAL SQLSTATE '45000' SET message_text = errormsg;
    ELSE
      SELECT title INTO main_post_title
      FROM posts
      WHERE id = NEW.reply_to;
      IF main_post_title IS NULL
      THEN
        SET errormsg = "Отговарите могат да са само към главно съобщение";
	SIGNAL SQLSTATE '45000' SET message_text = errormsg;
      END IF;
    END IF;
  ELSE
    IF NEW.reply_to IS NOT NULL
    THEN
      SET errormsg = "Отговорите не могат да предефинират заглавието";
      SIGNAL SQLSTATE '45000' SET message_text = errormsg;
    END IF;
  END IF;
END//
DELIMITER ;

Нека вмъкнем примерни данни. В случая взехме няколко произволни съобщения от форума OFFRoad-Bulgaria.com. Изтеглете файла със заявките от тук (ако не виждате текста – кодировката на файла е UTF8).

Ето една най-проста примерна заявка – изкарайте информация за тема 10 и всички нейни отговори, като сортирате по дата на въвеждане в базата от данни:

SELECT IF(posts.title IS NOT NULL, posts.title, 'REPLY') AS title, 
       users.username AS author, posts.created AS created, posts.data AS contents
FROM posts JOIN users ON posts.author = users.id
WHERE posts.id = 10 OR posts.reply_to = 10
ORDER BY posts.created\G

Сега да пристъпим към същината на темата – fulltext търсене. Досега знаем как можем да търсим шаблони в текстови полета чрез използване на оператор LIKE. Например ако искаме да намерим идентификационните номера на всички съобщения, които съдържат думата „Лада“ някъде в името на заглавието (ако е основно съобщение, защото отговорите нямат заглавия) или някъде в съдържанието си, то можем да направим следното:

SELECT id FROM posts
WHERE title LIKE '%Лада%' OR data LIKE '%Лада%';
+----+
| id |
+----+
|  1 |
|  5 |
+----+
2 rows in set (0.00 sec)

Това е много лошо решение, защото:

  • Търсенето по този начин е изключително бавно. Дори да съществуват индекси те никога няма да се използват. Проверете – направете индекс по колона „title“ и направете EXPLAIN на заявката – ще видите, че този индекс няма да присъства дори в „possible keys“. Причината е, че нашия шаблон започва с „%“;
  • Няма никакви критерии за сравнение „кой от резултатите е по-точен“. Ако например намерените резултати са два идентификационни номера „X“ и „Y“, то не можем да кажем, кое от двете попадения е по-подходящо за нас;
  • Следствие от недостатъка на предишната точка е, че при нарастването на базата от данни ще имаме все повече и повече резултати при стандартни търсения по обикновена дума и резултатните ни таблици ще стават все по-големи. При липсата на очевидна подредба потребителят ще се „загуби“ в резултатите. Няма никаква гаранция, че това, което излиза първо ще е това, което е „най-точно“ попадение.

С други думи на този етап ние искаме по някакъв начин да разширим стандартното търсене в текста с нещо допълнително – критерий за сравнение между резултатите (за да можем да ги подреждаме преди да ги изпратим към потребителя) и наличието на индекси (с което търсенето да бъде бързо). Решението e:

Създаване на Fulltext индекс

За да създадете въпросния индекс трябва да изпълните следната команда:

ALTER TABLE posts ADD FULLTEXT(title, data);

Като колони може да подавате само CHAR, VARCHAR или *TEXT типове данни. Впоследствие вие може да търсите чрез командата „MATCH (…) AGAINST (…)“ по колоните описани в индекса. Например ако искаме да направим нещо подобно на предложеното по-горе търсене, то ще напишем:

SELECT id FROM posts
WHERE MATCH(title, data) AGAINST ('Лада');
+----+
| id |
+----+
|  1 |
|  5 |
+----+
2 rows in set (0.00 sec)

Тук обаче нещата са се получили по доста различен начин. Въпросният индекс всъщност се състои от думи, тяхната „тежест“ и референции към редовете, в които се съдържат. Тежестта на всяка дума зависи от това „в колко от редовете е налична“ (по-рядка дума ще има по-голяма тежест и обратно). Наличието на думи от търсената фраза в текста ще добавят различни „тегла“. Именно общото тегло можем да използваме, за да сортираме нашите заявки. Ето как можем да видим въпросните тегла:

SELECT id, MATCH(title, data) AGAINST('Лада') AS score
FROM posts
WHERE MATCH(title, data) AGAINST ('Лада')
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
|  1 | 1.0845428705215454 |
|  5 | 0.9728003144264221 |
+----+--------------------+
2 rows in set (0.00 sec)

Имайте предвид, че в реално приложение няма нужда да слагате MATCH…AGAINST във връщаните колони и ORDER BY score в заявката. Ако те липсват, то MySQL автоматично ще сортира резултатната таблица по същия начин, както показания по-горе.

Колкото по-голяма е общата тежест, толкова по-добро е попадението. Наличието на MATCH…AGAINST… два пъти (един път в колоните на SELECT и един път в WHERE) НЕ добавя допълнително натоварване – MySQL ще направи търсенето само веднъж и ще го преизползва.

В MySQL 5.6 има три вида търсене. Ще разгледаме всеки един от тях:

Търсене с естествен език (IN NATURAL LANGUAGE MODE)

Това е търсенето по подразбиране. Ако напишете MATCH(колони) AGAINST („text“), то все едно сте написали MATCH(колони) AGAINST („text“ IN NATURAL LANGUAGE MODE). Този метод за търсене е възможно най-елементарен – просто се добавя тежестта на всяко едно попадение към общата тежест за всеки ред. Отсъствието на дума НЕ намалява общата тежест. Нека го демонстрираме с още три примера свързани с предишния:

SELECT id, MATCH(title, data) AGAINST('мост') AS score
FROM posts
WHERE MATCH(title, data) AGAINST ('мост')
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
| 11 | 1.0986145734786987 |
| 10 | 0.9155634045600891 |
+----+--------------------+
2 rows in set (0.00 sec)

SELECT id, MATCH(title, data) AGAINST('мост Лада') AS score
FROM posts
WHERE MATCH(title, data) AGAINST ('мост Лада')
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
| 11 | 1.0986145734786987 |
|  1 | 1.0845428705215454 |
|  5 | 0.9728003144264221 |
| 10 | 0.9155634045600891 |
+----+--------------------+
4 rows in set (0.00 sec)

SELECT id, MATCH(title, data) AGAINST('мост Лада Нива') AS score
FROM posts
WHERE MATCH(title, data) AGAINST ('мост Лада Нива')
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
|  5 |   2.61989426612854 |
|  1 |  2.169085741043091 |
| 11 | 1.0986145734786987 |
| 10 | 0.9155634045600891 |
+----+--------------------+
4 rows in set (0.00 sec)

Какво се случи:

  • Думата „Лада“ е налична само в постове 1 и 5 (проверете);
  • Думата „мост“ е налична само в постове 10 и 11 (проверете);
  • При търсене на „мост Лада“ се взеха всички редове, които съдържат думите „мост“ и „Лада“ и всеки ред получи своята тежест спрямо наличието на думите в него;
  • При търсене на „мост Лада Нива“ ние добавихме още една ключова дума – „Нива“. Тя очевидно НЕ е налична в постове 10 и 11, но е налична в 1 и 5. Тя съответно добави по-голяма тежест при тях.

Все пак можем да забележим нещо важно – думите „Лада“ и „Нива“ са едновременно налични и в двете съобщения 1 и 5, но те получиха съвсем различни оценки. Това е така, понеже освен „тежестта на думата“ и нейното „наличие или отсъствие“ се пресмятат още допълнителни неща – колко пъти се срещат попаденията в реда и колко уникални думи има в реда.

Важно е да се отбележи, че не се търси по абсолютно всичко. Търсената фраза може да съдържа всякакви символи, но за „дума“ ще се разчете само наличието на букви, цифри, долна черта или една(!) единична кавичка. Думите се разделят чрез разделители. Стандартните разделители са празен интервал, запетайка и точка. Наличието на „специален символ“ (нито един от посочените дотук) също ще бъде счетен за разделител на думи. Тоест „мост$Лада“ ще е същото като „мост Лада“.

Допълнително важат и следните правила:

  • Всяка „прекалено кратка“ дума ще бъде пропусната (не оказва влияние на резултата). При InnoDB по подразбиране това са двубуквените думи. Възможно е да променяте тази минимална дължина на дума чрез променливата innodb_ft_min_token_size. Това ограничение е важно, за да се премахнат попадения от предлози, съюзи и частици като „на“, „ще“, „до“, „и“, „по“ и т.н. Имайте го предвид, понеже може да доведе до неочаквани резултати. Ако например сте направили минималната дължина на дума от четири букви (както например е по подразбиране в MyISAM), то търсенето на думата „УАЗ“ ще е невъзможно и няма да върне никакъв резултат (бел. ред. сега разбирате и причината, поради която в много сайтове когато търсите нещо се изисква „да въведете поне X символа“).;
  • Думи от т.нар. „стоп списък“ (stopwords) ще бъдат пропускани. Ще разгледаме този списък по-надолу.
  • Стандартно (освен ако не сте избрали специфичен collation) не се прави разлика между малки и големи букви;
  • Добре е да отбележим и една важна разлика между Full Text Search в InnoDB и MyISAM. При MyISAM ако една дума присъства в повече от 50% от редовете, то тя ще бъде пропусната (този процент може да се коригира). При InnoDB на този етап не е така.

Разширено търсене с естествен език (WITH QUERY EXPANSION)

Понякога търсените фрази съдържат рядко използвани ключови думи, които имат значение на синоними на други, които са с по-широк спектър. Нека например предположим, че един човек търси наличието на думата „21213“ и по стандартния метод тя връща само едно единствено попадение. Да, но реално за човека думата „21213“ всъщност означава „модел 21213 на Лада Нива“, т.е. той би очаквал да получи и статии свързани и с думите „Лада“ и „Нива“, а не само с търсената от него „21213“. Това е реален проблем, защото СУБД няма как да има информация за тази допълнителна връзка между думите.

Именно тук се намесва „разширеното търсене“. При него практически търсенето се извършва на две стъпки:

  1. На първата стъпка се намират редовете с търсената дума. От тях се подбират други ключови думи, които MySQL определя като „значими“ (с достатъчно голяма тежест, но не уникални, а общи за въпросните редове). Тези нови ключови думи се предполага, че са значими за редовете;
  2. Прави се повторно търсене, но този път се добавят и подбраните нови ключови думи.

Ето един пример:

SELECT id, MATCH(title, data) AGAINST('Уазка') AS score
FROM posts
WHERE MATCH(title, data) AGAINST ('Уазка')
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
| 10 | 1.4016318321228027 |
+----+--------------------+
1 row in set (0.00 sec)

SELECT id, MATCH(title, data) 
           AGAINST('Уазка' WITH QUERY EXPANSION) AS score
FROM posts
WHERE MATCH(title, data) 
      AGAINST ('Уазка' WITH QUERY EXPANSION)
ORDER BY score DESC;
+----+--------------------+
| id | score              |
+----+--------------------+
| 10 |   72.7892074584961 |
| 11 |  5.612302303314209 |
|  8 | 3.1514804363250732 |
|  3 |  2.191800594329834 |
|  6 |  1.733217716217041 |
|  2 | 0.8657563924789429 |
|  7 | 0.7457319498062134 |
+----+--------------------+
7 rows in set (0.02 sec)

Както се вижда ясно думата „Уазка“ е налична само в една статия, но при разширеното търсене бяха отсети още няколко такива. Излезли са допълнителни статии, при това никак не лошо подредени по значимост. По този начин СУБД успя да свърже „Уазка“ с „УАЗ“ и някои други ключови думи.

От друга страна трябва да знаете, че разширеното търсене почти винаги вмъква много „шум“ в резултатите, т.е. излизат резултати, които наистина нямат нищо общо с търсеното. По-често тяхната обща тежест е ниска (можете ги отрежете с LIMIT или с HAVING(score>…)), но понякога може да получат и незаслужено висока оценка. Принципното правило за зависимостта между „шума“ и „правилните резултати“ е следното: колкото по-кратка е фразата, толкова по-малък шум се очаква и обратно – при дълги фрази очаквайте по-голям шум.

Булево търсене (IN BOOLEAN MODE)

Булевото търсене вече ни дава част от възможностите на истинска „търсачка“ така, както я познаваме от Google, Yahoo, Altavista и подобни. При него важат същите правила, както при нормалното търсене с естествен език, но има допълнителни оператори, които ни помагат значително да прецизираме. Те са следните:

  • Без оператор: Думата може да присъства (ще придаде тежест), но може и да не присъства. Абсолютно аналогично е като резултат при нормалното търсене – между търсените думи във фразата се слага логическо „или“;
  • Оператор „+“: Поставяйки „+“ пред дума в търсената фраза ние указваме, че думата е задължителна. Например ако търсим за „Лада Нива“, то стандартно бихме могли да получим попадения съдържащи само „Лада“ и съдържащи само „Нива“. Ако търсим за „+Лада +Нива“, то ние указваме, че е напълно задължително и двете думи да присъстват в резултатните редове;
  • Оператор „-„: Действието му е точно обратното на „+“ – този оператор указва, че указаната дума е задължително да НЕ присъства в резултатните редове. Например „+Лада -Калина“ ще върне всички редове с дума „Лада“, но от тях ще пропусне тези, в които присъства дума „Калина“;
  • Оператор „~“: С него се прави „обратна тежест“, т.е. думата след него ще намали резултатите, вместо да ги увеличи. Един вид това е по-слабо действие спрямо оператор „-“ – не желаем напълно за изключваме резултатите, но все пак ги отчитаме като по-лоши;
  • Оператор „@<разстояние>“: Указва минимално разстояние (в байтове), което трябва да е между думите. Например ако търсим за ‘ „Лада Нива“ @20’, то системата ще даде тежест на думите „Лада“ и „Нива“, но само тогава, когато те са на разстояние повече от 20 байта една от друга. Задължително е да ограждате ключовите думи (може да са повече от две) с двойни кавички и да слагате оператора след тях. Този оператор е удобен тогава, когато вие желаете да взимате резултати по търсените думи, но искате да избегнете конкретна често срещана комбинация от тях. Този оператор е наличен само за InnoDB;
  • Ограждащи скоби „(“ и „)“: Групират думи в „подизрази“. Използва се в комбинация с друг оператор, за да се даде общо значение за две фрази. Например „+(Лада Нива)“ ще изиска и двете думи да са налични (все едно сме сложили + и пред двете);
  • Оператори „>“ и „<„: Увеличава или намалява тежестта на думата. Например търсенето на „+Двигател +(>бензинов <дизелов)“ ще намери резултати едновременно за бензиновите и за дизеловите двигатели, но тези, които са бензинови ще получат по-голяма тежест;
  • Оператор „*“: Има действието на „%“ при търсене с LIKE, с тази разлика, че може да се използва само в края на думата. Тоест този оператор указва, че търсената дума „започва с…“. Например ако търсите „УАЗ*“, то ще получите и резултати „УАЗ“, „УАЗа“ и „УАЗка“ (и др. думи започващи с „уаз“, ако има такива). Тук „прекалено късите“ думи не се пропускат!;
  • Точна фраза: Когато оградите фраза с двойни кавички, то ще търсите „точно съвпадение“. Изключение се прави само ако е използван оператора за разстояние (описан по-горе).

ВАЖНО: При използване на режим „IN BOOLEAN MODE“ НЕ се извършва автоматично сортиране!!!

Примери:

1. Търсим статиите с точна фраза „Лада Нива“, но без тези, които включват думата „метан“:

SELECT id, MATCH(title, data)
           AGAINST('"Лада Нива" -метан' IN BOOLEAN MODE) AS score
FROM posts
WHERE MATCH(title, data)
      AGAINST('"Лада Нива" -метан'  IN BOOLEAN MODE)
ORDER BY score DESC;

2. Търсим статиите, в които се коментира едновременно УАЗ и ГАЗ (и техните вариации като УАЗ469, ГАЗ66 и т.н.):

SELECT id, MATCH(title, data)
           AGAINST('+(УАЗ* ГАЗ*)' IN BOOLEAN MODE) AS score
FROM posts
WHERE MATCH(title, data)
      AGAINST('+(УАЗ* ГАЗ*)' IN BOOLEAN MODE)
ORDER BY score DESC;

Стоп думи (STOPWORDS)

Както беше споменато в началото, „стоп думите“ са такива, които се срещат често в езика и би следвало да се пропускат като незначителни. MySQL има малък набор от такива думи на английски език. Те се намират в база от данни „information_schema“ в таблица „innodb_ft_default_stopword“. Ще видите, че таблицата има единствена колона с име „value“ и е от тип „varchar(30)“.

Ако желаете вие сами да си съставите списък със „стоп думи“, то е необходимо да създадете нова таблица със същата структура, както innodb_ft_default_stopword, т.е.:

CREATE TABLE yourdb.stopwords(
  value VARCHAR(30) PRIMARY KEY
)ENGINE = InnoDB;

След което е нужно да пренасочите глобалната променлива innodb_ft_server_stopword_table да сочи към нея:

SET innodb_ft_server_stopword_table = "yourdb/stopwords";

Разбира се може да укажете да се зарежда винаги при стартиране на сървъра чрез my.cnf.

Ограничения

На този етап (към версия 5.6.5) има редица ограничения към използването на Fulltext търсене. Ето трите най-съществени от тях:

  • Не се поддъжа при partitioning;
  • В InnoDB може да правите не повече от един Full-Text индекс на таблица;
  • Ако правите индекс по повече от една колона (както е в примера по-горе), то всички колони трябва да са от един и същи тип и collation;
  • В Against може да се подава само константен низ (т.е. не може да се подават стойности от променливи).

Зад сцената

За разлика от MyISAM, където индекса се поддържа като специално B+ дърво, в InnoDB се използват помощни релационни таблици. Това разбира се има своите предимства и недостатъци. Предимствата са по-голяма скалируемост и евентуална паралелизация, от което и лесно поддържане на конкурентни трансакции (при MyISAM трансакции няма, с което и B+ дървото излиза като по-добрия избор). В случая обаче е важно да разберем по какъв начин се обновява индекса при различните видове операции за промяна на данните:

  • Промяна на индекса само при достигане COMMIT: Индекса се променя винаги само и единствено при завършване на трансакция. Междинните промени вътре в трансакция не се отчитат. Ако имате трансакция, в която вмъквате/променяте/изтривате данни, то вие няма да видите промени във Full-Text индекса дори вътре в самата транкация. Ще видите промените чак след като я завършите с команда COMMIT;
  • INSERT заявки: При завършване на insert заявка промените във Full-Text индекса се записват в специален кеш, който по подразбиране е 32MB и се контролира от променливата innodb_ft_cache_size. Този кеш се прехвърля в „индексните таблици“ на дисковото устройство тогава, когато се запълни (или когато сървъра получи команда за изключване). Ако сървъра бъде спрян по неестествен начин, то при следващото включване се извършва синхронизация на кеша с физически записаните данни;
  • UPDATE заявки: Обновяването на индексните таблици се извършва директно върху индексните таблици при достигането на COMMIT на трансакцията. Това е „скъпа операция“, защото често налага обновяване на множество редове от тях;
  • DELЕTE заявки: Операциите за изтриване са доста бързи, понеже реално не се изтриват редове от индексните таблици. Вместо това се записва уникалния номер (FTS_DOC_ID – виж следващия раздел) в специална помощна таблица за „изтрити редове“. Впоследствие, когато се прави търсене, MySQL ще се обърне към тази таблица с изтрити редове, за да ги филтрира от резултатната таблица. Така DELЕTE заявките стават прости и бързи, понеже въобще не променят индекса, но от друга страна при продължително изпълнение на такива се получава постоянно намаляване на бързодействието на MATCH… AGAINST заявките, както и заемане на все по-голямо и по-голямо количество информация (индексите продължително растат). Решението на този проблем е да изтриете индекса и да го създадете наново. Това може да отнеме значително време при големи таблици. Алтернативно може да дадете стойност „1“ на глобалната променлива innodb_optimize_fulltext_only, след което да извършите команда OPTIMIZE TABLE… на въпросната таблица. Така ще се изпълнят до „innodb_ft_num_word_optimize“ на брой (по подразбиране 2000) промени по индекса.

Допълнителни уточнения

Поддържането на Full-Text индекс е „скъпа“ операция, която отнема доста ресурси. Забележете, че вмъкването или промяната на информация в таблица води до мащабно преструктуриране и обновяване на целия индекс. Поради тази причина, особено при големи обеми от данни, може да се окаже така, че при добавянето или променянето на няколко реда в таблица ще е по-добре да премахнете индекса, да добавите данните и след това да създадете индекса отново. Това често се използва от големите системи – при тях програмните продукти правят промените в таблиците на т.нар. „batches“, при които Full-Text индексите се пресъздават на всеки „batch“.

Full-Text индексите използват колона в таблицата с име „FTS_DOC_ID“ (винаги с главни букви) от тип BIGINT UNSIGNED с AUTO_INCREMENT (източник). В бъдеще се предвижда да може да указвате и друга колона с произволно име, която да замести FTS_DOC_ID. Ако такава колона няма, то ще бъде създадена и по нея ще бъде създаден индекс (но създадената автоматично колона ще бъде „скрита“ за вас, т.е. няма да я виждате). В редица случаи може да сметнете за подходящо вие сами да си създадете такава колона, особено тогава, когато вашата таблица има нужда от първичен ключ с „id“-та – тогава ще можете да споделите тази колона както за ваш първичен ключ, така и за Full-Text индекса. Освен това се използва и уникален индекс с име FTS_DOC_ID_INDEX, които също може да създадете. Или казано по друг начин – ако използвате Full-Text индекси, то е добре вашата таблица да има следната структура:

CREATE TABLE tbl(
FTS_DOC_ID BIGINT unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (FTS_DOC_ID),
...
) ENGINE=InnoDB;

CREATE UNIQUE INDEX FTS_DOC_ID_INDEX ON tbl(FTS_DOC_ID);

Не е задължително FTS_DOC_ID да е PRIMARY KEY – може спокойно да използвате друг ключ. В този случай FTS_DOC_ID трябва допълнително да го ограничите с NOT NULL и UNIQUE (за да предотвратите вмъкването на невалидни данни впоследствие).

Друга оптимизация може да се получи при използване на InnoDB с Full-Text индекс на многопроцесорни (многоядрени) компютри (забележете, че това е нововъведение и MyISAM не се възползва от многопроцесорните системи при FT индекси). Броят на нишките, които работят паралелно с Full-Text индекса се контролира от променливата InnoDB_ft_sort_pll_degree (по подразбиране със стойност 2, т.е. 2 нишки). Възможните стойности могат да са степени на двойката и е препоръчително да изберете по-голям или равен на броя на процесорите (ядрата).

Използването на оператор „@<разстояние>“ в булевото търсене на този етап прави заявките за търсене значително по-бавни. При оператор „*“ също се наблюдава забавяне спрямо другите оператори, но определено е на приемливо ниво.

Естествено може да създавате Full-Text индекс и при самото създаване на таблицата (т.е. без да използвате ALTER TABLE, както беше в примера по-горе). Това става чрез:

CREATE TABLE tbl(
...
FULLTEXT(<колона/и>),
...
) ENGINE=InnoDB;

Или алтернативно на ALTER TABLE може да добавите индекса чрез специална заявка:

CREATE FULLTEXT INDEX idx 
ON <таблица>(<колона/и>);

Задача: В примерната база от данни направете така, че отговорите на статии да приемат автоматично заглавието на главната статия, на която те отговарят.

 



Добави коментар

Адресът на електронната поща няма да се публикува


*