J'ai récemment expérimenté Redis et MongoDB et il semblerait qu'il y ait souvent des cas où vous stockeriez un tableau d' identifiants dans MongoDB ou Redis. Je vais m'en tenir à Redis pour cette question puisque je pose la question sur l' opérateur MySQL IN .
Je me demandais à quel point il était performant de lister un grand nombre (300-3000) d' identifiants dans l'opérateur IN, ce qui ressemblerait à ceci:
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
Imaginez quelque chose d'aussi simple qu'un tableau de produits et de catégories que vous pourriez normalement JOINDRE ensemble pour obtenir les produits d'une certaine catégorie . Dans l'exemple ci-dessus, vous pouvez voir que sous une catégorie donnée dans Redis ( category:4:product_ids
), je renvoie tous les identifiants de produit de la catégorie avec l'ID 4 et les place dans la SELECT
requête ci-dessus à l'intérieur de l' IN
opérateur.
À quel point est-ce performant?
Est-ce une situation «ça dépend»? Ou y a-t-il un concret "ceci est (in) acceptable" ou "rapide" ou "lent" ou devrais-je ajouter un LIMIT 25
, ou cela n'aide-t-il pas?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25
Ou devrais-je réduire le tableau des identifiants de produit renvoyés par Redis pour le limiter à 25 et ajouter seulement 25 identifiants à la requête plutôt que 3000 et le faire LIMIT
passer à 25 à l'intérieur de la requête?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)
Toutes les suggestions / commentaires sont très appréciés!
la source
id IN (1,2,3 ... 3000)
performance serait-elle comparée à la table JOIN deproducts_categories
. Ou est-ce ce que vous disiez?IN
clause (cela pourrait même être linéaire sur une liste triée comme vous le montrez, en fonction de l'algorithme), puis intersection / recherches linéaires .Réponses:
De manière générale, si la
IN
liste devient trop grande (pour une valeur mal définie de `` trop grande '' qui est généralement de l'ordre de 100 ou moins), il devient plus efficace d'utiliser une jointure, créant une table temporaire si besoin est pour tenir les chiffres.Si les nombres sont un ensemble dense (pas de lacunes - ce que les données d'exemple suggèrent), vous pouvez faire encore mieux avec
WHERE id BETWEEN 300 AND 3000
.Cependant, il y a probablement des lacunes dans l'ensemble, auquel cas il peut être préférable de suivre la liste des valeurs valides après tout (à moins que les lacunes ne soient relativement peu nombreuses, auquel cas vous pouvez utiliser:
Ou quelles que soient les lacunes.
la source
AND id NOT BETWEEN XXX AND XXX
ne fonctionnera pas et il vaut mieux s'en tenir à l'équivalent(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
comme l'a écrit @David Fells.J'ai fait quelques tests, et comme David Fells le dit dans sa réponse , c'est assez bien optimisé. Pour référence, j'ai créé une table InnoDB avec 1 000 000 registres et en faisant une sélection avec l'opérateur "IN" avec 500 000 nombres aléatoires, cela ne prend que 2,5 secondes sur mon MAC; sélectionner uniquement les registres pairs prend 0,5 seconde.
Le seul problème que j'ai eu est que j'ai dû augmenter le
max_allowed_packet
paramètre dumy.cnf
fichier. Sinon, une mystérieuse erreur «MYSQL est parti» est générée.Voici le code PHP que j'utilise pour faire le test:
$NROWS =1000000; $SELECTED = 50; $NROWSINSERT =15000; $dsn="mysql:host=localhost;port=8889;dbname=testschema"; $pdo = new PDO($dsn, "root", "root"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec("drop table if exists `uniclau`.`testtable`"); $pdo->exec("CREATE TABLE `testtable` ( `id` INT NOT NULL , `text` VARCHAR(45) NULL , PRIMARY KEY (`id`) )"); $before = microtime(true); $Values=''; $SelValues='('; $c=0; for ($i=0; $i<$NROWS; $i++) { $r = rand(0,99); if ($c>0) $Values .= ","; $Values .= "( $i , 'This is value $i and r= $r')"; if ($r<$SELECTED) { if ($SelValues!="(") $SelValues .= ","; $SelValues .= $i; } $c++; if (($c==100)||(($i==$NROWS-1)&&($c>0))) { $pdo->exec("INSERT INTO `testtable` VALUES $Values"); $Values = ""; $c=0; } } $SelValues .=')'; echo "<br>"; $after = microtime(true); echo "Insert execution time =" . ($after-$before) . "s<br>"; $before = microtime(true); $sql = "SELECT count(*) FROM `testtable` WHERE id IN $SelValues"; $result = $pdo->prepare($sql); $after = microtime(true); echo "Prepare execution time =" . ($after-$before) . "s<br>"; $before = microtime(true); $result->execute(); $c = $result->fetchColumn(); $after = microtime(true); echo "Random selection = $c Time execution time =" . ($after-$before) . "s<br>"; $before = microtime(true); $sql = "SELECT count(*) FROM `testtable` WHERE id %2 = 1"; $result = $pdo->prepare($sql); $result->execute(); $c = $result->fetchColumn(); $after = microtime(true); echo "Pairs = $c Exdcution time=" . ($after-$before) . "s<br>";
Et les résultats:
Insert execution time =35.2927210331s Prepare execution time =0.0161771774292s Random selection = 499102 Time execution time =2.40285992622s Pairs = 500000 Exdcution time=0.465420007706s
la source
%
) avec un opérateur égal (=
) au lieu deIN()
.Vous pouvez créer une table temporaire dans laquelle vous pouvez placer n'importe quel nombre d'ID et exécuter une requête imbriquée. Exemple:
CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));
et sélectionnez:
SELECT id, name, price FROM products WHERE id IN (SELECT ID FROM tmp_IDs);
la source
L'utilisation
IN
avec un grand jeu de paramètres sur une grande liste d'enregistrements sera en fait lente.Dans le cas que j'ai résolu récemment, j'avais deux clauses where, l'une avec 2,50 paramètres et l'autre avec 3 500 paramètres, interrogeant une table de 40 millions d'enregistrements.
Ma requête a pris 5 minutes en utilisant la norme
WHERE IN
. En utilisant à la place une sous-requête pour IN instruction (en plaçant les paramètres dans leur propre table indexée), j'ai réduit la requête à DEUX secondes.J'ai travaillé pour MySQL et Oracle dans mon expérience.
la source
IN
c'est bien, et bien optimisé. Assurez-vous de l'utiliser sur un champ indexé et tout va bien.C'est fonctionnellement équivalent à:
En ce qui concerne le moteur DB.
la source
IN
utilise des optimisations pour de meilleures performances.Lorsque vous fournissez de nombreuses valeurs pour
IN
opérateur, il doit d'abord les trier pour supprimer les doublons. Au moins je soupçonne cela. Il ne serait donc pas bon de fournir trop de valeurs, car le tri prend N log N temps.Mon expérience a prouvé que le découpage de l'ensemble de valeurs en sous-ensembles plus petits et la combinaison des résultats de toutes les requêtes dans l'application donnent les meilleures performances. J'avoue avoir acquis de l'expérience sur une base de données différente (Pervasive), mais la même chose peut s'appliquer à tous les moteurs. Mon nombre de valeurs par ensemble était de 500 à 1000. Plus ou moins était significativement plus lent.
la source