Comment optimiser SELECT très lent avec LEFT JOINs sur de grandes tables


14

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)

Vidage complet ~ 280 Mo

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?


4
C'est le prix que vous payez pour la conception d'un EAV. Vous voudrez peut-être essayer un index composite surattribute (person_id, attribute_type_id, attribute_value)
mustaccio

1
J'essaierais d'ajouter ces index: (attribute_type_id, attribute_value, person_id)et (attribute_type_id, person_id, attribute_value)
ypercubeᵀᴹ

5
Et utilisez InnoDB, jetez MyISAM. Nous sommes en 2015, MyiSAM est mort depuis longtemps.
ypercubeᵀᴹ

2
Première chose - débarrassez-vous de la jointure LEFT, cela n'a aucun effet car vous utilisez toutes les tables dans votre condition WHERE, transformant efficacement toutes les jointures en jointures INNER (l'optimiseur devrait être en mesure de comprendre et d'optimiser cela, mais mieux de ne pas le rendre plus difficile). ). Deuxième chose - désactivez le cache de requêtes à moins que vous n'ayez une bonne raison de l'utiliser (= vous l'avez testé et vous avez mesuré qu'il vous aide)
jkavalik

2
OT: n'est-il pas étrange que vous utilisiez LIMIT sans notre commande par? Cela renverra quelques 100000 lignes aléatoires?
ibre5041

Réponses:


7

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.


3

Ajouter des indéces à attributepour:

  • (person_id, attribute_type_id, attribute_value) et
  • (attribute_type_id, attribute_value, person_id)

Explication

Avec votre conception actuelle, EXPLAINvotre requête doit examiner les 1,265,229 * 4 * 4 * 4 = 80,974,656lignes dans attribute. Vous pouvez réduire ce nombre en ajoutant un index composite sur attributepour (person_id, attribute_type_id). En utilisant cet index, votre requête examinera seulement 1 au lieu de 4 lignes pour chacun des location, eyecoloret gender.

Vous pouvez étendre cet indice à inclure attribute_type_valueainsi: (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 incluant person_id) devrait améliorer les performances par rapport à la simple utilisation d'un index sur attribute_valuelequel 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 de bornyear.

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:

+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+
| id | select_type | table    | type   | possible_keys                       | key               | key_len | ref                            |    rows | filtered | Extra                    |
+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+
|  1 | SIMPLE      | bornyear | range  | person_type_value,type_value_person | type_value_person |       9 |                                | 1861881 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | location | ref    | person_type_value,type_value_person | person_type_value |       8 | bornyear.person_id,const       |       1 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | eyecolor | ref    | person_type_value,type_value_person | person_type_value |       8 | bornyear.person_id,const       |       1 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | gender   | ref    | person_type_value,type_value_person | person_type_value |      13 | bornyear.person_id,const,const |       1 |   100.00 | Using index              |
|  1 | SIMPLE      | person   | eq_ref | PRIMARY                             | PRIMARY           |       4 | bornyear.person_id             |       1 |   100.00 | Using index              |
+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+

1
Merci pour la réponse et l'explication détaillées. J'ai fait tout ce que vous avez mentionné mais la requête prend toujours ~ 2 min. S'il vous plaît, quel type de table (innodb, myisam) utilisez-vous et quelle requête exacte effectuée?
Martin

1
Outre l'ajout des indécis, j'ai utilisé exactement les mêmes données et définitions que vous, donc j'ai utilisé MyISAM. J'ai changé la première ligne de votre requête SELECT person.person_idcar sinon elle ne s'exécuterait pas, évidemment. L'avez-vous fait ANALYZE TABLE attributeaprès avoir ajouté les indéces? Vous pouvez également ajouter votre nouvelle EXPLAINsortie (après avoir ajouté des indécis) à votre question.
wolfgangwalther

3

Je suppose qu'il peut y avoir un problème avec la conception de la base de données.

Vous utilisez une conception dite Entity-Attribute-Value, qui fonctionne souvent mal, bien, par conception.

Avez-vous des suggestions pour mieux concevoir cette situation, s'il vous plaît?

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 GenderIDdans la persontable, 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éfinir NULLlorsque 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é genderqui relie à person1: 1 et n'a des lignes que pour les personnes qui ont un sexe connu.

Des considérations similaires s'appliquent à eyecoloret bornyear- il est peu probable que la personne ait deux valeurs pour un eyecolorou bornyear.

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 tableaux personet locationseraient liés 1: M.


Ou tout simplement pour ajuster la sélection ci-dessus?

Si vous utilisez la conception EAV, je ferais au moins ce qui suit.

  • Set colonnes attribute_type_id, attribute_value, person_idà NOT NULL.
  • Configurez la clé étrangère qui est liée attribute.person_idà person.person_id.
  • Créez un index sur trois colonnes (attribute_type_id, attribute_value, person_id). L'ordre des colonnes est important ici.
  • Pour autant que je sache, MyISAM n'honore pas les clés étrangères, alors ne l'utilisez pas, utilisez plutôt InnoDB.

J'écrirais la requête comme ceci. Utilisez à la INNERplace des LEFTjointures et écrivez explicitement une sous-requête pour chaque attribut pour donner à l'optimiseur toutes les chances d'utiliser l'index.

SELECT person.person_id
FROM
    person
    INNER JOIN
    (
        SELECT attribute.person_id
        FROM attribute
        WHERE attribute_type_id = 1
            AND location.attribute_value BETWEEN 3000 AND 7000
    ) AS location ON location.person_id = person.person_id
    INNER JOIN
    (
        SELECT attribute.person_id
        FROM attribute
        WHERE attribute_type_id = 2
            AND location.attribute_value = 1
    ) AS gender ON gender.person_id = person.person_id
    INNER JOIN
    (
        SELECT attribute.person_id
        FROM attribute
        WHERE attribute_type_id = 3
            AND location.attribute_value BETWEEN 1980 AND 2000
    ) AS bornyear ON bornyear.person_id = person.person_id
    INNER JOIN
    (
        SELECT attribute.person_id
        FROM attribute
        WHERE attribute_type_id = 4
            AND location.attribute_value IN (2, 3)
    ) AS eyecolor ON eyecolor.person_id = person.person_id
LIMIT 100000;

En outre, il peut être utile de partitionner la attributetable par attribute_type_id.


Attention aux performances: JOIN ( SELECT ... )n'optimise pas bien. JOINingdirectement sur la table fonctionne mieux (mais reste problématique).
Rick James

2

J'espère avoir trouvé une solution suffisante. Il s'inspire de cet article .

Réponse courte:

  1. J'ai créé 1 table avec tous les attributs. Une colonne pour un attribut. Plus la colonne de clé primaire.
  2. Les valeurs d'attribut sont stockées dans des cellules de texte (pour la recherche en texte intégral) au format CSV.
  3. Création d'index en texte intégral. Avant cela, il est important de définir ft_min_word_len=1(pour MyISAM) dans la [mysqld]section et innodb_ft_min_token_size=1(pour InnoDb) dans le my.cnffichier, redémarrez le service mysql.
  4. Exemple de recherche: SELECT * FROM person_index WHERE MATCH(attribute_1) AGAINST("123 456 789" IN BOOLEAN MODE) LIMIT 1000123, 456a 789sont des identifiants auxquels les personnes devraient être associées attribute_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.

CREATE TABLE `person_attribute_ft` (
  `person_id` int(11) NOT NULL,
  `attr_1` text,
  `attr_2` text,
  `attr_3` text,
  `attr_4` text,
  PRIMARY KEY (`person_id`),
  FULLTEXT KEY `attr_1` (`attr_1`),
  FULLTEXT KEY `attr_2` (`attr_2`),
  FULLTEXT KEY `attr_3` (`attr_3`),
  FULLTEXT KEY `attr_4` (`attr_4`),
  FULLTEXT KEY `attr_12` (`attr_1`,`attr_2`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

É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:

INSERT IGNORE INTO `person_attribute_ft`
SELECT
    p.person_id,
    (SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 1 AND a.person_id = p.person_id LIMIT 10) attr_1,
    (SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 2 AND a.person_id = p.person_id LIMIT 10) attr_2,
    (SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 3 AND a.person_id = p.person_id LIMIT 10) attr_3,
    (SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 4 AND a.person_id = p.person_id LIMIT 10) attr_4
FROM person p

Le résultat devrait être quelque chose comme ceci:

mysql> select * from person_attribute_ft limit 10;
+-----------+--------+--------+--------+--------+
| person_id | attr_1 | attr_2 | attr_3 | attr_4 |
+-----------+--------+--------+--------+--------+
|         1 | 541    | 2      | 1927   | 3      |
|         2 | 2862   | 2      | 1939   | 4      |
|         3 | 6573   | 2      | 1904   | 2      |
|         4 | 2432   | 1      | 2005   | 2      |
|         5 | 2208   | 1      | 1995   | 4      |
|         6 | 8388   | 2      | 1973   | 1      |
|         7 | 107    | 2      | 1909   | 4      |
|         8 | 5161   | 1      | 2005   | 1      |
|         9 | 8022   | 2      | 1953   | 4      |
|        10 | 4801   | 2      | 1900   | 3      |
+-----------+--------+--------+--------+--------+
10 rows in set (0.00 sec)

Étape 3. Sélectionnez dans le tableau avec une requête comme celle-ci:

mysql> SELECT SQL_NO_CACHE *
    -> FROM `person_attribute_ft`
    -> WHERE 1 AND MATCH(attr_1) AGAINST ("3000 3001 3002 3003 3004 3005 3006 3007" IN BOOLEAN MODE)
    -> AND MATCH(attr_2) AGAINST ("1" IN BOOLEAN MODE)
    -> AND MATCH(attr_3) AGAINST ("1980 1981 1982 1983 1984" IN BOOLEAN MODE)
    -> AND MATCH(attr_4) AGAINST ("2,3" IN BOOLEAN MODE)
    -> LIMIT 10000;
+-----------+--------+--------+--------+--------+
| person_id | attr_1 | attr_2 | attr_3 | attr_4 |
+-----------+--------+--------+--------+--------+
|     12131 | 3002   | 1      | 1982   | 2      |
|     51315 | 3007   | 1      | 1984   | 2      |
|    147283 | 3001   | 1      | 1984   | 2      |
|    350086 | 3005   | 1      | 1982   | 3      |
|    423907 | 3004   | 1      | 1982   | 3      |
... many rows ...
|   9423907 | 3004   | 1      | 1982   | 3      |
|   9461892 | 3007   | 1      | 1982   | 2      |
|   9516361 | 3006   | 1      | 1980   | 2      |
|   9813933 | 3005   | 1      | 1982   | 2      |
|   9986892 | 3003   | 1      | 1981   | 2      |
+-----------+--------+--------+--------+--------+
90 rows in set (0.17 sec)

La requête sélectionne toutes les lignes:

  • correspondant à au moins un de ces ID dans attr_1:3000, 3001, 3002, 3003, 3004, 3005, 3006 or 3007
  • ET au correspondant même temps 1dans attr_2(cette colonne représente le genre donc si cette solution a été personnalisée, il devrait être smallint(1)avec l' index simple, etc ...)
  • Et en même temps correspondant à au moins un 1980, 1981, 1982, 1983 or 1984enattr_3
  • Et dans le même correspondant de temps 2ou 3dansattr_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.


1
Je trouve très peu probable que cette conception fonctionne mieux que votre conception d'origine avec des index composites. Quels tests avez-vous faits pour les comparer?
ypercubeᵀᴹ

0

Essayez d'utiliser des indices d'index de requête qui semblent appropriés

Conseils sur l'index Mysql


1
Les conseils peuvent aider une version de la requête, mais en blesser une autre. Notez que l'Optimiseur a choisi bornyear comme meilleure première table, probablement parce que s'il était filtré les lignes les plus indésirables.
Rick James
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.