La recherche en texte intégral se traduit par une grande quantité de temps passé en «initialisation complète»

12

J'essaie actuellement d'exécuter des requêtes sur un vidage de données des commentaires de Stack Overflow. Voici à quoi ressemble le schéma:

CREATE TABLE `socomments` (
  `Id` int(11) NOT NULL,
  `PostId` int(11) NOT NULL,
  `Score` int(11) DEFAULT NULL,
  `Text` varchar(600) NOT NULL,
  `CreationDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `UserId` int(11) NOT NULL,
  PRIMARY KEY (`Id`),
  KEY `idx_socomments_PostId` (`PostId`),
  KEY `CreationDate` (`CreationDate`),
  FULLTEXT KEY `Text` (`Text`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

J'ai exécuté cette requête sur la table, et elle a été incroyablement lente (elle contient 29 millions de lignes, mais elle a un index de texte intégral):

SELECT *
FROM socomments
WHERE MATCH (Text) AGAINST ('"fixed the post"' IN BOOLEAN MODE)

Je l'ai donc profilé, dont les résultats sont:

|| Status                     || Duration ||
|| starting                   || 0.000058 ||
|| checking permissions       || 0.000006 ||
|| Opening tables             || 0.000014 ||
|| init                       || 0.000019 ||
|| System lock                || 0.000006 ||
|| optimizing                 || 0.000007 ||
|| statistics                 || 0.000013 ||
|| preparing                  || 0.000005 ||
|| FULLTEXT initialization    || 207.1112 ||
|| executing                  || 0.000009 ||
|| Sending data               || 0.000856 ||
|| end                        || 0.000004 ||
|| query end                  || 0.000004 ||
|| closing tables             || 0.000006 ||
|| freeing items              || 0.000059 ||
|| logging slow query         || 0.000037 ||
|| cleaning up                || 0.000046 ||

Comme vous pouvez le voir, il passe beaucoup de temps à l'initialisation FULLTEXT. Est-ce normal? Sinon, comment pourrais-je le réparer?

hichris123
la source
Idée: créez un deuxième tableau dans lequel vous mettez tous les 1 000 commentaires dans un champ de texte. Maintenant, vous recherchez d'abord dans ce deuxième tableau et vous obtenez par exemple id_group 2et id_group 23. Avec cela, votre recherche à l'intérieur de votre table principale et limitez votre requête aux plages d'ID 2.000 à 2.999 et 23.000 à 23.999. Bien sûr, le 2e produira plus de résultats au fur et à mesure que vous mélangerez tous les commentaires en créant de nouvelles combinaisons de mots clés, mais finalement cela devrait accélérer le tout. Bien sûr, cela double l'utilisation de l'espace disque. De nouveaux commentaires devraient être CONCAT'és à la table de groupe.
mgutt

Réponses:

5

D'autres ont trouvé que c'était une situation gênante

Étant donné que la documentation MySQL est très concise sur cet état de thread

Initialisation FULLTEXT

Le serveur se prépare à effectuer une recherche en texte intégral en langage naturel.

votre seul recours serait de vous préparer avec moins de données. Comment ?

SUGGESTION # 1

Examinez à nouveau votre requête. Il sélectionne toutes les colonnes. Je voudrais refactoriser la requête pour collecter uniquement les colonnes id socomments. Ensuite, joignez ces identifiants récupérés à la socommentstable.

SELECT B.* FROM
(SELECT id FROM socomments
WHERE MATCH (Text) AGAINST ('"fixed the post"' IN BOOLEAN MODE)) A
LEFT JOIN socomments B USING (id);

Cela pourrait produire un plan EXPLAIN plus laid mais je pense que le profilage changera pour le mieux. L'idée de base est la suivante: si vous avez une recherche FULLTEXT agressive, faites-la recueillir le moins de données pendant cette FULLTEXT initializationphase, réduisant ainsi le temps.

Je l'ai déjà recommandé plusieurs fois

SUGGESTION # 2

Veuillez vous assurer que vous définissez les options FULLTEXT basées sur InnoDB, pas celles pour MyISAM. Les deux options qui devraient vous préoccuper sont

Pensez-y un instant. Le champ de texte est VARCHAR (600). Disons que la moyenne est de 300 octets. Vous en avez 29 000 000 millions. Ce serait un peu 8 Go. Peut-être que l'augmentation de innodb_ft_cache_size et innodb_ft_total_cache_size peut également aider.

Assurez-vous que vous disposez de suffisamment de RAM pour les plus grands tampons InnoDB FULLTEXT.

ESSAIE !!!

RolandoMySQLDBA
la source
J'ai essayé les deux suggestions, cela a réduit le temps d'environ 10 secondes à 200 secondes. Ce qui est étrange, c'est que le pool de tampons n'est qu'à 9% d'utilisation ...
hichris123
Essayez de mettre un signe plus à l'intérieur de la partie CONTRE: SELECT B.* FROM (SELECT id FROM socomments WHERE MATCH (Text) AGAINST ('+"fixed the post"' IN BOOLEAN MODE)) A LEFT JOIN socomments B USING (id);et voyez si cela fait une différence.
RolandoMySQLDBA
La raison pour laquelle j'ai suggéré un signe plus? Doc ( dev.mysql.com/doc/refman/5.6/en/fulltext-boolean.html ) dit A leading or trailing plus sign indicates that this word must be present in each row that is returned. InnoDB only supports leading plus signs.Dans votre cas particulier, la phrase exacte fixed the postdoit exister.
RolandoMySQLDBA
Mêmes résultats. Peu plus rapide et plus lent, donc probablement juste en raison de différences infimes dans le moment où il a été exécuté.
hichris123
5

Si vous utilisez des index InnoDB FULLTEXT, les requêtes se bloquent souvent dans l'état "FULLTEXT initialization" si vous interrogez une table contenant un grand nombre de lignes supprimées. Dans l'implémentation FULLTEXT d'InnoDB, les lignes supprimées ne sont pas élaguées jusqu'à ce qu'une opération OPTIMIZE ultérieure soit exécutée sur la table affectée. Voir: https://dev.mysql.com/doc/refman/5.6/en/innodb-fulltext-index.html

Pour supprimer les entrées d'index de texte intégral pour les enregistrements supprimés, vous devez exécuter OPTIMIZE TABLE sur la table indexée avec innodb_optimize_fulltext_only = ON pour reconstruire l'index de texte intégral.

On peut également inspecter le nombre d'enregistrements supprimés mais non purgés en interrogeant information_schema.innodb_ft_deleted

Pour résoudre ce problème, il convient d'exécuter régulièrement OPTIMIZE TABLE sur des tables avec des index InnoDB FULLTEXT.

Tyler
la source
Je reçois la logique à ce sujet, mais pouvez-vous vérifier cela innodb_optimize_fulltext_only=1et une OPTIMIZEtable prend réellement en charge les lignes supprimées "en attente"? dba.stackexchange.com/questions/174486/…
Riedsio
0

Les index de texte intégral dans MySQL ne sont pas conçus pour prendre en charge de grandes quantités de données, donc la vitesse de recherche diminue assez rapidement à mesure que votre ensemble de données grandit. L'une des solutions consiste à utiliser des moteurs de recherche en texte intégral externes comme Solr ou Sphinx qui ont des fonctionnalités de recherche améliorées (optimisation de la pertinence et prise en charge de la recherche de phrases, facettes intégrées, extraits de code, etc.), syntaxe de requête étendue et vitesse beaucoup plus rapide à mi-chemin -grands ensembles de données.

Solr est basé sur la plate-forme Java, donc si vous exécutez une application basée sur Java est un choix naturel pour vous, Sphinx est écrit en C ++ et agit comme un démon de la même manière que MySQL. Dès que vous alimentez le moteur externe avec les données que vous souhaitez rechercher, vous pouvez également déplacer certaines requêtes hors de MySQL. Je ne peux pas vous dire quel moteur est meilleur dans votre cas, j'utilise principalement Sphinx et voici un exemple d'utilisation: http://astellar.com/2011/12/replacing-mysql-full-text-search-with-sphinx/

vfedorkov
la source