MySQL - NEREDEN SEÇİLİR alan IN (alt sorgu) - Son derece yavaş neden?


133

Bir veritabanında incelemek istediğim birkaç kopyası var, bu yüzden kopyaları görmek için ne yaptım, bunu yaptım:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

Bu şekilde, related_field ile tüm satırları birden çok kez alırım. Bu sorgunun yürütülmesi milisaniye sürer.

Şimdi, yinelenen her incelemek istedim, bu yüzden yukarıdaki sorguda bir related_field ile some_table her satırı SEÇEBİLİR düşündüm, bu yüzden böyle yaptım:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Bu bir sebepten ötürü aşırı yavaş olduğu ortaya çıkıyor (dakikalar alıyor). Onu yavaşlatmak için tam olarak neler oluyor? related_field dizine eklenir.

Sonunda ilk sorgudan bir görünüm "temp_view" oluşturma (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)ve daha sonra bunun gibi ikinci sorgumu yapmaya çalıştı :

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

Ve bu gayet iyi çalışıyor. MySQL bunu birkaç milisaniye içinde yapar.

Burada neler olup bittiğini açıklayan herhangi bir SQL uzmanı var mı?


tam olarak ne istiyorsun? biri dışında yinelenen girişleri silmek istiyorum ?? Öneri: Lütfen Kendi Kendine Katılın
diEcho

1
Açıkçası grup yavaş yavaş ...
ajreal

İlk sorgu milisaniye içinde yürütülür (HAVING ile gruplama ve filtreleme). Sadece her şeyi yavaşlatan diğer sorgu ile birlikte (dakikalar alır).
quano

@diEcho, yinelenenleri bulmak, incelemek ve bazılarını manuel olarak silmek istiyorum.
quano

Yanıtlar:


112

Sorguyu buna yeniden yazın

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

Bence seçimde st2.relevant_fieldolmalı, aksi takdirde havingyan tümce bir hata verecektir, ancak% 100 emin değilim

Asla INbir alt sorgu ile kullanmayın ; bu herkesin bildiği gibi yavaş.
Sadece INsabit bir değer listesiyle kullanın .

Diğer ipuçları

  1. Sorguları daha hızlı hale getirmek istiyorsanız, SELECT *yalnızca gerçekten ihtiyacınız olan alanları seçmeyin.
  2. relevant_fieldEşit birleşmeyi hızlandırmak için bir dizininiz olduğundan emin olun .
  3. group byBirincil anahtarın açık olduğundan emin olun .
  4. InnoDB üzerindeyseniz ve yalnızca dizinlenmiş alanları seçerseniz (ve işler çok karmaşık değilse) MySQL, sorgunuzu yalnızca dizinleri kullanarak çözerek işleri hızlandırır.

IN (select Sorgularınızın % 90'ı için genel çözüm

Bu kodu kullan

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

1
Bunu ile de yazabilirsiniz HAVING COUNT(*) > 1. MySQL'de genellikle daha hızlıdır.
ypercubeᵀᴹ

@ ypercube, alt sorgu için yapılan, en iyi sorgu için sonucu değiştireceğini düşünüyorum.
Johan

@Johan: yana st2.relevant_fielddeğil NULL(zaten dahil oluyor ONfıkra), bu sonucu değiştirmez.
ypercubeᵀᴹ

Eğer saymak içine sayımı (uzaklara) değiştirebilirsiniz böylece @ypercube, (*) eğer sen emin afieldasla null, anlaşıldı. Teşekkürler
Johan

1
@quano, evet tüm kopyaları listeler çünkü group byaçık st1.id, açık değil st1.relevant_field.
Johan

110

Alt sorgu, ilişkili bir sorgu olduğu için her satır için çalıştırılmaktadır. Alt sorgudan her şeyi seçerek, ilişkili olmayan bir sorguya ilişkili bir sorgu yapılabilir, şöyle:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

Son sorgu şöyle görünecektir:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

3
Bu benim için inanılmaz iyi çalıştı. Bir IN (alt sorgu) içinde başka bir IN (alt sorgu) vardı ve o kadar uzun süre beklerken googled 10 dakikadan fazla sürüyordu. Önerdiğiniz gibi her alt sorguyu SELECT * FROM () içinde sarma, 2 saniyeye düşürdü!
Liam

TEŞEKKÜR EDERİM, bunu birkaç saattir yapmak için iyi bir yol bulmaya çalışıyorum. Bu mükemmel çalıştı. Size daha fazla oy verebilir diliyorum! Bu kesinlikle cevap olmalı.
thaspius

Mükemmel çalışıyor. Çalıştırılması ~ 50 saniye süren bir sorgu şimdi anlık. Keşke daha fazla oy verebilseydim. Bazen birleşimleri kullanamazsınız, bu yüzden doğru cevap budur.
simon

Optimizer'ın neden sendikalarla ilişkili sorguları düşündüğünü merak ediyorum ... Her neyse, bu hile sihir gibi çalıştı
Brian Leishman

2
Bunu ilişkili bir alt sorguyu yapan şeyin ne olduğunu açıklar mısınız? Benim dış sorguya bağlı bir değer kullandığında, alt sorgu ilişkili olur anlayışım. Ancak bu örnekte herhangi bir bağımlılık göremiyorum. Dış sorgu tarafından döndürülen her satır için aynı sonucu verir. MariaDB'de uygulanan benzer bir örneğim var ve hiçbir performans isabeti göremiyorum (şimdiye kadar), bu yüzden bu SELECT *sarma gerektiğinde açıkça görmek istiyorum .
sbnc.eu

6

Alt sıraların her satır için çalıştırıldığından şüphe ettim.
quano

Bazı MySQL Sürümleri IN'de bir İndeks kullanmıyor. Başka bir bağlantı ekledim.
edze

1
MySQL 6 henüz kararlı değil, bunu üretim için tavsiye etmem!
Johan

1
Bunu tavsiye etmem. Ancak burada dahili olarak nasıl çalıştığı açıklanmaktadır (4.1 / 5.x -> 6). Bu, mevcut sürümlerin bazı tuzaklarını göstermektedir.
edze

5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

Sorgularımı veritabanlarımdan birinde denedim ve ayrıca bir alt sorgunun birleşimi olarak yeniden yazmayı denedim.

Bu çok daha hızlı çalıştı, deneyin!


Evet, bu muhtemelen grup sonuçlarıyla geçici bir tablo oluşturacaktır, bu nedenle görünüm sürümü ile aynı hızda olacaktır. Ancak sorgu planları gerçeği söylemelidir.
ypercubeᵀᴹ

3

Bunu dene

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

2

Yavaş sql sorgunuzu www.prettysql.net ile yeniden biçimlendirdim

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

Hem sorguda hem de alt sorguda bir tablo kullanırken, her zaman aşağıdaki gibi ikisini birden takma ad kullanmanız gerekir:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

Bu yardımcı olur mu?


1
Ne yazık ki yardımcı olmuyor. O kadar yavaş çalışır.
quano

Cevabımı güncelledim, tekrar deneyebilir misin? Tarafından grup yavaş olsa bile, sadece bir kez yürütülmelidir ...
plang

Geçen sefer yanlışlıkla canlı bir mysql sunucusunu öldürdüm, bu yüzden şu anda bunu deneyemiyorum. Daha sonra bir test veritabanı ayarlamam gerekecek. Ama bunun neden sorguyu etkilemesi gerektiğini anlamıyorum. HAVING ifadesi yalnızca içerdiği sorgu için geçerli olmalıdır, değil mi? Gerçekten neden "gerçek" sorgu alt sorguyu etkilemesi gerektiğini anlamıyorum.
quano

Bunu buldum: xaprb.com/blog/2006/04/30/… . Bence bu çözüm olabilir. Zaman bulduğumda deneyeceğim.
quano

2

Öncelikle yinelenen satırları bulabilir ve satır sayısını kaç kez kullanıldığını bulabilir ve bu sayıya göre sıralayabilirsiniz;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Bundan sonra bir tablo oluşturun ve sonuca bir tablo ekleyin.

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Son olarak, genel satırları silin. 0 başlamaz. Her grubun ilk numarası hariç tüm genel satırları silin.

delete from  CopyTable where No!= 0;


1

bazen veri büyüdükçe mysql NEREDE sorgu optimizasyonu nedeniyle oldukça yavaş olabilir. Mysql'ye sorguyu olduğu gibi yürütmesini bildirmek için STRAIGHT_JOIN kullanmayı deneyin, örn.

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

ama dikkat: çoğu durumda mysql optimizer oldukça iyi çalışır, bu yüzden sadece bu tür bir sorun olduğunda kullanmanızı tavsiye ederim


0

Bu benim durumuma benzer, burada bir tablo var tabel_buku_besar. İhtiyacım olan şey

  1. Kayıt için Looking olduğunu account_code='101.100'içinde tabel_buku_besarhangi gelmiş companyarea='20000've aynı zamanda sahip IDRolduğucurrency

  2. tabel_buku_besarAdım 1 ile aynı account_code var ama transaction_numberadım 1 sonuç var tüm kayıt almak gerekir

kullanırken select ... from...where....transaction_number in (select transaction_number from ....), sorgum son derece yavaş çalışıyor ve bazen istek zaman aşımına neden oluyor veya uygulamamın yanıt vermemesine neden oluyor ...

Bu kombinasyonu deniyorum ve sonuç ... fena değil ...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

0

Ben bir değer olup olmadığını bulmak için en verimli bulmak, mantık kolayca bir değer yoksa (yani IS NULL) bulmak için ters çevrilebilir;

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

* Related_field öğesini tablonuzda varlığını kontrol etmek istediğiniz değerin adıyla değiştirin

* PrimaryKey'i karşılaştırma tablosundaki birincil anahtar sütununun adıyla değiştirin.

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.