MySQL "IN" operatörü (büyük?) Sayıda değerde performansı


94

Son zamanlarda Redis ve MongoDB ile deneyler yapıyorum ve çoğu zaman MongoDB veya Redis'de bir dizi kimliği saklayacağınız durumlar var gibi görünüyor . MySQL IN operatörü hakkında soru sorduğum için bu soru için Redis'e bağlı kalacağım .

IN operatörü içinde çok sayıda (300-3000) id listelemenin ne kadar performanslı olduğunu merak ediyordum , bu şuna benzer bir şeye benzeyecektir:

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)

Belirli bir kategorideki ürünleri almak için normalde bir araya getirebileceğiniz ürünler ve kategoriler tablosu kadar basit bir şey hayal edin . Yukarıdaki örnekte, Redis ( ) içindeki belirli bir kategori altında , 4 numaralı kategorideki tüm ürün kimliklerini döndürdüğümü ve bunları operatörün içindeki yukarıdaki sorguya yerleştirdiğimi görebilirsiniz .category:4:product_idsSELECTIN

Bu ne kadar başarılı?

Bu "duruma göre değişir" bir durum mu? Veya somut bir "bu kabul edilebilir (değil)" veya "hızlı" veya "yavaş" var mı, yoksa bir eklemeli miyim LIMIT 25, yoksa bu yardımcı olmuyor mu?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25

Yoksa Redis tarafından döndürülen ürün kimliği dizisini 25 ile sınırlandırmalı ve sorguya 3000 yerine 25 kimlik eklemeli ve sorgu içinden 25'e eklemeli LIMITmiyim?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)

Herhangi bir öneri / geri bildirim çok takdir edilmektedir!


Tam olarak ne istediğinden emin değilim? "İd IN (1,2,3, ... 3000))" olan bir sorgu, "id = değer" ile 3000 sorgudan daha hızlıdır. Ancak "kategori = 4" ile bir birleştirme yukarıdakilerin her ikisinden de daha hızlı olacaktır.
Ronnis

Doğru, yine de bir ürün birden fazla kategoriye ait olabileceğinden "kategori = 4" yapamazsınız. Redis'i kullanarak, belirli kategorilere ait ürünlerin tüm kimliklerini kaydeder ve ardından sorgularım. Sanırım asıl soru, id IN (1,2,3 ... 3000)JOIN tablosuna kıyasla performans nasıl olurdu products_categories? Yoksa söylediğin bu muydu?
Michael van Rooijen


Elbette, bunun indekslenmiş satırları almanın diğer herhangi bir yöntemi kadar verimli olmaması için hiçbir neden yoktur; bu sadece veritabanı yazarlarının test edip, optimize edip etmediğine bağlıdır. Hesaplama karmaşıklığı açısından en kötü ihtimalle INcümle üzerinde bir O (n log N) sıralaması yapacağız (bu, algoritmaya bağlı olarak gösterdiğiniz gibi sıralanmış bir listede doğrusal bile olabilir) ve ardından doğrusal kesişim / aramalar .
jberryman

Yanıtlar:


40

Genel olarak konuşursak, INliste çok genişlerse (genellikle 100 veya daha küçük bölgede olan bazı kötü tanımlanmış 'çok büyük' ​​değeri için), bir birleşim kullanmak daha verimli hale gelir ve gerekirse geçici bir tablo oluşturur numaraları tutmak için.

Sayılar yoğun bir kümeyse (boşluk yok - örnek verilerin önerdiği gibi), o zaman ile daha da iyisini yapabilirsiniz WHERE id BETWEEN 300 AND 3000.

Bununla birlikte, muhtemelen kümede boşluklar vardır, bu noktada sonuçta geçerli değerler listesiyle gitmek daha iyi olabilir (boşluklar nispeten az değilse, bu durumda kullanabilirsiniz:

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836

Veya boşluklar ne olursa olsun.


46
Lütfen "birleştirme kullan, geçici bir tablo oluştur" örneğini verebilir misiniz?
Jake

veri seti bir arayüzden (çoklu seçim öğesi) geldiyse ve seçilen verilerde boşluklar varsa ve bu boşluklar sıralı bir boşluk değilse (eksik: 457, 490, 658, ..) o AND id NOT BETWEEN XXX AND XXXzaman işe yaramayacak ve daha iyi (x = 1 OR x = 2 OR x = 3 ... OR x = 99)@David Fells'in yazdığı eşdeğeri tut.
deepcell

Tecrübelerime göre - e-ticaret web sitelerinde çalışırken, ~ 50 alakasız ürün kimliğinin arama sonuçlarını göstermemiz gerekiyor, "1. 50 ayrı sorgu" ile "2." içinde birçok değer içeren bir sorgu "karşısında daha iyi sonuçlar elde ettik "" maddesi. Şu an için bunu kanıtlayacak bir yolum yok, ancak # 2 sorgusu izleme sistemlerimizde her zaman yavaş bir sorgu olarak görünecek, oysa yürütme miktarına bakılmaksızın # 1 hiçbir zaman görünmeyecek. milyonlarca ... aynı deneyime sahip olan var mı? (belki daha iyi önbelleğe alma ile ilişkilendirebiliriz veya diğer sorguların sorgular arasında geçiş yapmasına izin verebiliriz ...)
Chaim Klar

24

Bazı testler yapıyorum ve David Fells'in cevabında söylediği gibi, oldukça iyi optimize edilmiş. Referans olarak, 1.000.000 kayıt içeren bir InnoDB tablosu oluşturdum ve "IN" operatörü ile 500.000 rastgele sayı ile bir seçim yaptım, MAC'imde sadece 2,5 saniye sürüyor; sadece çift kayıtların seçilmesi 0,5 saniye sürer.

Sahip olduğum tek sorun, max_allowed_packetparametreyi my.cnfdosyadan yükseltmem gerektiğiydi . Değilse, gizemli bir "MYSQL gitti" hatası üretilir.

Testi yapmak için kullandığım PHP kodu:

$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>";

Ve sonuçlar:

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s

Başkalarının iyiliği için, bir i7 ile 2013 Sonu MBP'mde VirtualBox'ta (CentOS) çalışmayı ekleyeceğim, çıktının üçüncü satırı (soruyla ilgili olan): Rastgele seçim = 500744 Zaman yürütme süresi = 53.458173036575s .. 53 saniye uygulamanıza bağlı olarak tolere edilebilir. Benim kullanımım için, gerçekten değil. Ayrıca, çift sayılar için yapılan testin eldeki soruyla alakalı olmadığını unutmayın çünkü modulo %operatörünü ( =) yerine eşittir operatörü ( ) ile kullanır IN().
rinogo

Alakalı, çünkü bir sorguyu IN operatörüyle benzer bir sorgu ile bu işlevsellik olmadan karşılaştırmanın bir yoludur. Aldığınız daha yüksek zaman, bir indirme süresi olduğu için olabilir, çünkü makineniz değiştiriliyor veya başka bir sanal makinede çalışıyor.
jbaylina

14

İstediğiniz sayıda kimlik koyabileceğiniz ve iç içe sorgu çalıştırabileceğiniz geçici bir tablo oluşturabilirsiniz Örnek:

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));

ve seçin:

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);

6
alt sorgu kullanmak yerine geçici tablonuza katılmak daha iyidir
scharette

3
@loopkin, bunu bir alt sorguya karşılık bir alt sorgu ile nasıl yapacağınızı açıklar mısınız?
Jeff Solomon

3
@jeffSolomon products.id, ad, fiyat SEÇİN products.id = tmp_IDs.ID'de tmp_ID'lere KATILIN;
scharette

BU CEVAP! aradığım şeydi, uzun kayıtlar için çok çok hızlı
Damián Rafael Lattenero

Çok teşekkür ederim dostum. İnanılmaz derecede hızlı çalışıyor.
mrHalfer

4

INBüyük bir kayıt listesinde büyük bir parametre kümesiyle kullanmak aslında yavaş olacaktır.

Son zamanlarda çözdüğüm durumda, biri 2,50 parametreli ve diğeri 3,500 parametreli, 40 Milyon kayıtlık bir tabloyu sorgulayan iki where cümlesine sahiptim.

Sorgum standardı kullanarak 5 dakika sürdü WHERE IN. Bunun yerine IN için bir alt sorgu kullanarak ifadesi (parametreleri kendi indekslenmiş tablolarına koyarak), sorguyu İKİ saniyeye indirdim.

Deneyimlerime göre hem MySQL hem de Oracle için çalıştı.


1
"Bunun yerine IN ifadesi için bir alt sorgu kullanarak (parametreleri kendi indekslenmiş tablolarına koyarak)" konusunu anlamadım. "NEREDE KİMLİK GİRİŞİ (1,2,3)" yerine "NEREDE KİMLİK GİRİŞİ (xxx KİMLİĞİ SEÇ)" kullanmamız gerektiğini mi söylediniz?
Istiyak Tailor

4

INiyi ve iyi optimize edilmiş. Dizine alınmış bir alanda kullandığınızdan ve iyi olduğunuzdan emin olun.

İşlevsel olarak şuna eşdeğerdir:

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)

DB motoru söz konusu olduğunda.


1
Gerçek değil. DB'den 5k kayıt almak için IN clouse kullanıyorum. IN clouse, PK'lerin listesini içerir, bu nedenle ilgili sütun endekslenir ve benzersiz olması garanti edilir. EXPLAIN, tam tablo taramasının PK araması kullanılarak "fifo-queue-benzer" tarzında yapıldığını söylüyor.
Antoniossss

MySQL'de "işlevsel olarak eşdeğer" olduklarına inanmıyorum . INdaha iyi performans için optimizasyonları kullanır.
Joshua Pinter

1
Josh, cevap 2011'den geldi - O zamandan beri bazı şeylerin değiştiğinden eminim, ancak gün içinde IN bir dizi OR ifadesine dönüştü.
David Fells

1
Bu cevap doğru değil. Gönderen Yüksek Performanslı MySQL O kadar MySQL, IN () listedeki değerleri sıralar ve bir değer listesinde olup olmadığını görmek için hızlı ikili arama kullanır. Bu, listenin boyutunda O (log n) iken, eşdeğer bir OR cümleleri dizisi, liste boyutunda O (n) 'dir (yani, büyük listeler için çok daha yavaştır).
Bert

Bert - evet. Bu cevap geçersizdir. Bir düzenleme önermekten çekinmeyin.
David Fells

-2

INOperatör için birçok değer sağladığınızda, önce kopyaları kaldırmak için sıralaması gerekir. En azından bundan şüpheleniyorum. Bu nedenle, sıralama N log N süresi alacağından, çok fazla değer sağlamak iyi olmaz.

Deneyimlerim, değer kümesini daha küçük alt kümelere bölmenin ve uygulamadaki tüm sorguların sonuçlarını birleştirmenin en iyi performansı verdiğini kanıtladı. Farklı bir veritabanında (Yaygın) deneyim topladığımı kabul ediyorum, ancak aynı şey tüm motorlar için geçerli olabilir. Set başına değer sayım 500-1000 idi. Aşağı yukarı önemli ölçüde daha yavaştı.


Bunun 7 yıl sonra olduğunu biliyorum, ancak bu yanıtla ilgili sorun basitçe bunun eğitimli bir tahmine dayalı bir yorum olması.
Giacomo1968
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.