J'étais sur Google, autodidacte et à la recherche d'une solution pendant des heures, mais sans chance. J'ai trouvé quelques questions similaires ici, mais pas ce cas.
Mes tables:
- personnes (~ 10 millions de lignes)
- attributs (lieu, âge, ...)
- liens (M: M) entre les personnes et les attributs (~ 40M lignes)
Situation:
j'essaie de sélectionner tous les identifiants de personne ( person_id
) à partir de certains endroits ( location.attribute_value BETWEEN 3000 AND 7000
), étant un sexe ( gender.attribute_value = 1
), né dans certaines années ( bornyear.attribute_value BETWEEN 1980 AND 2000
) et ayant la couleur de certains yeux ( eyecolor.attribute_value IN (2,3)
).
C'est ma requête qui a pris 3 à 4 minutes. et j'aimerais optimiser:
SELECT person_id
FROM person
LEFT JOIN attribute location ON location.attribute_type_id = 1 AND location.person_id = person.person_id
LEFT JOIN attribute gender ON gender.attribute_type_id = 2 AND gender.person_id = person.person_id
LEFT JOIN attribute bornyear ON bornyear.attribute_type_id = 3 AND bornyear.person_id = person.person_id
LEFT JOIN attribute eyecolor ON eyecolor.attribute_type_id = 4 AND eyecolor.person_id = person.person_id
WHERE 1
AND location.attribute_value BETWEEN 3000 AND 7000
AND gender.attribute_value = 1
AND bornyear.attribute_value BETWEEN 1980 AND 2000
AND eyecolor.attribute_value IN (2,3)
LIMIT 100000;
Résultat:
+-----------+
| person_id |
+-----------+
| 233 |
| 605 |
| ... |
| 8702599 |
| 8703617 |
+-----------+
100000 rows in set (3 min 42.77 sec)
Expliquez étendu:
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| 1 | SIMPLE | bornyear | range | attribute_type_id,attribute_value,person_id | attribute_value | 5 | NULL | 1265229 | 100.00 | Using where |
| 1 | SIMPLE | location | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | eyecolor | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | gender | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.eyecolor.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | test1.location.person_id | 1 | 100.00 | Using where; Using index |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
5 rows in set, 1 warning (0.02 sec)
Profilage:
+------------------------------+-----------+
| Status | Duration |
+------------------------------+-----------+
| Sending data | 3.069452 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.968915 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.042468 |
| Waiting for query cache lock | 0.000043 |
| Sending data | 3.264984 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.823919 |
| Waiting for query cache lock | 0.000038 |
| Sending data | 2.863903 |
| Waiting for query cache lock | 0.000014 |
| Sending data | 2.971079 |
| Waiting for query cache lock | 0.000020 |
| Sending data | 3.053197 |
| Waiting for query cache lock | 0.000087 |
| Sending data | 3.099053 |
| Waiting for query cache lock | 0.000035 |
| Sending data | 3.064186 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.939404 |
| Waiting for query cache lock | 0.000018 |
| Sending data | 3.440288 |
| Waiting for query cache lock | 0.000086 |
| Sending data | 3.115798 |
| Waiting for query cache lock | 0.000068 |
| Sending data | 3.075427 |
| Waiting for query cache lock | 0.000072 |
| Sending data | 3.658319 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.335427 |
| Waiting for query cache lock | 0.000049 |
| Sending data | 3.319430 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.496563 |
| Waiting for query cache lock | 0.000029 |
| Sending data | 3.017041 |
| Waiting for query cache lock | 0.000032 |
| Sending data | 3.132841 |
| Waiting for query cache lock | 0.000050 |
| Sending data | 2.901310 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.107269 |
| Waiting for query cache lock | 0.000062 |
| Sending data | 2.937373 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.097082 |
| Waiting for query cache lock | 0.000261 |
| Sending data | 3.026108 |
| Waiting for query cache lock | 0.000026 |
| Sending data | 3.089760 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 3.012763 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 3.069694 |
| Waiting for query cache lock | 0.000046 |
| Sending data | 3.591908 |
| Waiting for query cache lock | 0.000060 |
| Sending data | 3.526693 |
| Waiting for query cache lock | 0.000076 |
| Sending data | 3.772659 |
| Waiting for query cache lock | 0.000069 |
| Sending data | 3.346089 |
| Waiting for query cache lock | 0.000245 |
| Sending data | 3.300460 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.135361 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.909447 |
| Waiting for query cache lock | 0.000039 |
| Sending data | 3.337561 |
| Waiting for query cache lock | 0.000140 |
| Sending data | 3.138180 |
| Waiting for query cache lock | 0.000090 |
| Sending data | 3.060687 |
| Waiting for query cache lock | 0.000085 |
| Sending data | 2.938677 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 2.977974 |
| Waiting for query cache lock | 0.000872 |
| Sending data | 2.918640 |
| Waiting for query cache lock | 0.000036 |
| Sending data | 2.975842 |
| Waiting for query cache lock | 0.000051 |
| Sending data | 2.918988 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.943810 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.330211 |
| Waiting for query cache lock | 0.000025 |
| Sending data | 3.411236 |
| Waiting for query cache lock | 0.000023 |
| Sending data | 23.339035 |
| end | 0.000807 |
| query end | 0.000023 |
| closing tables | 0.000325 |
| freeing items | 0.001217 |
| logging slow query | 0.000007 |
| logging slow query | 0.000011 |
| cleaning up | 0.000104 |
+------------------------------+-----------+
100 rows in set (0.00 sec)
Structures des tables:
CREATE TABLE `attribute` (
`attribute_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`attribute_type_id` int(11) unsigned DEFAULT NULL,
`attribute_value` int(6) DEFAULT NULL,
`person_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`attribute_id`),
KEY `attribute_type_id` (`attribute_type_id`),
KEY `attribute_value` (`attribute_value`),
KEY `person_id` (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=40000001 DEFAULT CHARSET=utf8;
CREATE TABLE `person` (
`person_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`person_name` text CHARACTER SET latin1,
PRIMARY KEY (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8;
La requête avait été effectuée sur le serveur virtuel DigitalOcean avec SSD et 1 Go de RAM.
Je suppose qu'il peut y avoir un problème avec la conception de la base de données. Avez-vous des suggestions pour mieux concevoir cette situation, s'il vous plaît? Ou tout simplement pour ajuster la sélection ci-dessus?
attribute (person_id, attribute_type_id, attribute_value)
(attribute_type_id, attribute_value, person_id)
et(attribute_type_id, person_id, attribute_value)
Réponses:
Choisissez quelques attributs à inclure
person
. Indexez-les en quelques combinaisons - utilisez des index composites, pas des index à colonne unique.C'est essentiellement le seul moyen de sortir de l'EAV-sucks-at-performance, qui est là où vous êtes.
Voici plus de discussion: http://mysql.rjweb.org/doc.php/eav, y compris une suggestion d'utiliser JSON au lieu de la table de valeurs-clés.
la source
Ajouter des indéces à
attribute
pour:(person_id, attribute_type_id, attribute_value)
et(attribute_type_id, attribute_value, person_id)
Explication
Avec votre conception actuelle,
EXPLAIN
votre requête doit examiner les1,265,229 * 4 * 4 * 4 = 80,974,656
lignes dansattribute
. Vous pouvez réduire ce nombre en ajoutant un index composite surattribute
pour(person_id, attribute_type_id)
. En utilisant cet index, votre requête examinera seulement 1 au lieu de 4 lignes pour chacun deslocation
,eyecolor
etgender
.Vous pouvez étendre cet indice à inclure
attribute_type_value
ainsi:(person_id, attribute_type_id, attribute_value)
. Cela transformerait cet index en un index de couverture pour cette requête, ce qui devrait également améliorer les performances.De plus, l'ajout d'un index
(attribute_type_id, attribute_value, person_id)
(à nouveau un indice de couverture en incluantperson_id
) devrait améliorer les performances par rapport à la simple utilisation d'un index surattribute_value
lequel davantage de lignes devraient être examinées. Dans ce cas, cela accélérera la première étape de votre explication: sélectionner une plage debornyear
.L'utilisation de ces deux indéces a réduit le temps d'exécution de votre requête sur mon système de ~ 2,0 s à ~ 0,2 s avec la sortie d'explication ressemblant à ceci:
la source
SELECT person.person_id
car sinon elle ne s'exécuterait pas, évidemment. L'avez-vous faitANALYZE TABLE attribute
après avoir ajouté les indéces? Vous pouvez également ajouter votre nouvelleEXPLAIN
sortie (après avoir ajouté des indécis) à votre question.Vous utilisez une conception dite Entity-Attribute-Value, qui fonctionne souvent mal, bien, par conception.
La manière relationnelle classique de concevoir cela serait de créer une table distincte pour chaque attribut. En général, vous pouvez avoir ces tables séparées:
location
,gender
,bornyear
,eyecolor
.Les éléments suivants varient selon que certains attributs sont toujours définis pour une personne ou non. Et, si une personne ne peut avoir qu'une seule valeur d'un attribut. Par exemple, la personne n'a généralement qu'un seul sexe. Dans votre conception actuelle, rien ne vous empêche d'ajouter trois lignes pour la même personne avec des valeurs de sexe différentes. Vous pouvez également définir une valeur de genre non pas à 1 ou 2, mais à un nombre qui n'a pas de sens, comme 987 et aucune contrainte dans la base de données ne l'empêcherait. Mais, c'est une autre question distincte de maintenir l'intégrité des données avec la conception EAV.
Si vous connaissez toujours le sexe de la personne, cela n'a aucun sens de la placer dans une table séparée et il est préférable d'avoir une colonne non nulle
GenderID
dans laperson
table, ce qui serait une clé étrangère de la table de recherche avec la liste des tous les genres possibles et leurs noms. Si vous connaissez le sexe de la personne la plupart du temps, mais pas toujours, vous pouvez rendre cette colonne annulable et la définirNULL
lorsque aucune information n'est disponible. Si la plupart du temps, le sexe de la personne n'est pas connu, il peut être préférable d'avoir un tableau séparégender
qui relie àperson
1: 1 et n'a des lignes que pour les personnes qui ont un sexe connu.Des considérations similaires s'appliquent à
eyecolor
etbornyear
- il est peu probable que la personne ait deux valeurs pour uneyecolor
oubornyear
.S'il est possible pour une personne d'avoir plusieurs valeurs pour un attribut, alors vous le mettriez certainement dans une table séparée. Par exemple, il n'est pas rare qu'une personne ait plusieurs adresses (domicile, travail, courrier, vacances, etc.), vous devez donc toutes les répertorier dans un tableau
location
. Les tableauxperson
etlocation
seraient liés 1: M.Si vous utilisez la conception EAV, je ferais au moins ce qui suit.
attribute_type_id
,attribute_value
,person_id
àNOT NULL
.attribute.person_id
àperson.person_id
.(attribute_type_id, attribute_value, person_id)
. L'ordre des colonnes est important ici.J'écrirais la requête comme ceci. Utilisez à la
INNER
place desLEFT
jointures et écrivez explicitement une sous-requête pour chaque attribut pour donner à l'optimiseur toutes les chances d'utiliser l'index.En outre, il peut être utile de partitionner la
attribute
table parattribute_type_id
.la source
JOIN ( SELECT ... )
n'optimise pas bien.JOINing
directement sur la table fonctionne mieux (mais reste problématique).J'espère avoir trouvé une solution suffisante. Il s'inspire de cet article .
Réponse courte:
ft_min_word_len=1
(pour MyISAM) dans la[mysqld]
section etinnodb_ft_min_token_size=1
(pour InnoDb) dans lemy.cnf
fichier, redémarrez le service mysql.SELECT * FROM person_index WHERE MATCH(attribute_1) AGAINST("123 456 789" IN BOOLEAN MODE) LIMIT 1000
où123
,456
a789
sont des identifiants auxquels les personnes devraient être associéesattribute_1
. Cette requête a pris moins de 1 seconde.Réponse détaillée:
Étape 1. Création d'une table avec des index de texte intégral. InnoDb prend en charge les index de texte intégral de MySQL 5.7, donc si vous utilisez 5.5 ou 5.6, vous devez utiliser MyISAM. C'est parfois encore plus rapide pour la recherche FT qu'InnoDb.
Étape 2. Insérez les données de la table EAV (entité-attribut-valeur). Par exemple indiqué en question cela peut se faire avec 1 SQL simple:
Le résultat devrait être quelque chose comme ceci:
Étape 3. Sélectionnez dans le tableau avec une requête comme celle-ci:
La requête sélectionne toutes les lignes:
attr_1
:3000, 3001, 3002, 3003, 3004, 3005, 3006 or 3007
1
dansattr_2
(cette colonne représente le genre donc si cette solution a été personnalisée, il devrait êtresmallint(1)
avec l' index simple, etc ...)1980, 1981, 1982, 1983 or 1984
enattr_3
2
ou3
dansattr_4
Conclusion:
Je sais que cette solution n'est pas parfaite et idéale pour de nombreuses situations, mais peut être utilisée comme une bonne alternative pour la conception de tables EAV.
J'espère que ça va aider quelqu'un.
la source
Essayez d'utiliser des indices d'index de requête qui semblent appropriés
Conseils sur l'index Mysql
la source