MySQL-SELECT WHEREフィールドIN(サブクエリ)-非常に遅いのはなぜですか?


133

検査したいデータベースに重複がいくつかあるので、どの重複を確認するために私はこれを行いました:

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

このようにして、related_fieldが複数回出現するすべての行を取得します。このクエリの実行には数ミリ秒かかります。

ここで、重複のそれぞれを検査したかったので、上記のクエリで関連するフィールドを使用してsome_tableの各行をSELECTできると思ったので、次のようにしました。

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

これは、何らかの理由で非常に遅いことがわかります(数分かかります)。それをそんなに遅くするためにここで正確に何が起こっているのですか?関連フィールドにはインデックスが付けられています。

最終的に、最初のクエリからビュー "temp_view"を作成してから、(SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)代わりに次のように2番目のクエリを作成しました。

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

そして、それはうまくいきます。MySQLはこれを数ミリ秒で実行します。

何が起こっているのかを説明できるSQLエキスパートはいますか?


正確に何をしたいですか?1つを除いて重複するエントリを削除しますか?提案:セルフジョイン
diEcho

1
明らかに遅いグループバイです...
ajreal '26年

最初のクエリはミリ秒単位で実行されます(1つはHAVINGによるグループ化とフィルタリング)。他のクエリと組み合わせた場合のみ、すべてが遅くなります(数分かかります)。
quano 2011年

@diEcho、重複を見つけて調べ、手動で削除したい。
quano 2011年

回答:


112

クエリをこれに書き換えます

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

そうでst2.relevant_fieldなければhaving句がエラーを出すので、私は選択にいる必要があると思いますが、100%確信はありません

INサブクエリでは使用しないでください。これは非常に遅いです。固定値リストで
のみ使用INします。

その他のヒント

  1. クエリを高速化したい場合は、SELECT *本当に必要なフィールドのみを選択しないでください。
  2. relevant_field等結合を高速化するためのインデックスがあることを確認してください。
  3. group by主キーを確認してください。
  4. InnoDB を使用していて、インデックス付きフィールドのみを選択している場合(そして物事はそれほど複雑ではありません)場合、MySQLはインデックスのみを使用してクエリを解決し、処理を高速化します。

あなたの90%のための一般的なソリューション IN (select クエリの

このコードを使用

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
それをで書くこともできHAVING COUNT(*) > 1ます。通常、MySQLでは高速です。
ypercubeᵀᴹ

@ypercube、下のクエリに対して行われた、私は上のクエリに対して結果を変更すると思います。
ヨハン

@Johan:st2.relevant_fieldisではないNULL(既にON句に含まれている)ため、結果は変更されません。
ypercubeᵀᴹ

@ypercube、それで、あなたがcount(afield)をcount(*)に変更することができるなら、それが決してないだろうと確信しているなら、それを得ました。ありがとうafieldnull
Johan

1
@quano、はい。ではなくがオンであるため、すべての重複がリストされます。group byst1.idst1.relevant_field
ヨハン

110

副照会は相関照会であるため、行ごとに実行されています。次のように、サブクエリからすべてを選択することにより、相関クエリを非相関クエリにすることができます。

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

最終的なクエリは次のようになります。

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
これは私には驚くほどうまくいきました。IN(サブクエリ)内に別のIN(サブクエリ)があり、10分以上かかっていたので、待機している間グーグルしていました。あなたが提案したように、SELECT * FROM()で各サブクエリをラップすると、2秒に短縮されます!
リアム

ありがとう、私はこれを数時間の間、これを行うための良い方法を考え出そうとしてきました。これは完全に機能しました。私はあなたにもっと賛成票を与えることができればいいのに!これは間違いなく答えになるはずです。
タスピウス2016年

完璧に動作します。実行に最大50秒かかったクエリが瞬時に実行されるようになりました。もっと賛成できるといいのに。結合を使用できない場合があるため、これが正しい答えです。
サイモン

なぜオプティマイザはユニオンの相関があるクエリを考慮するのでしょうか...とにかく、このトリックは魔法のように機能しました
ブライアンリーシュマン2017年

2
相関サブクエリの原因を説明していただけますか?外部クエリに依存する値を使用すると、サブクエリが相関するようになるという私の理解。しかし、この例では、相互依存関係は確認できません。外部クエリによって返される各行に対して同じ結果が得られます。MariaDBに実装されている同様の例があり、パフォーマンスへの影響はありません(これまでのところ)。そのため、このSELECT *ラッピングが必要な場合は、はっきりと確認したいと思います。
sbnc.eu

6

行ごとにサブクエリが実行されているのではないかと思いました。
quano 2011年

一部のMySQLバージョンでは、INでインデックスを使用していません。別のリンクを追加しました。
edze

1
MySQL 6はまだ安定していません。本番環境ではお勧めしません。
ヨハン

1
私はそれをお勧めしません。しかし、ここではそれが内部でどのように実行されるかを説明します(4.1 / 5.x-> 6)。これは、現在のバージョンのいくつかの落とし穴を示しています。
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;

私は私のデータベースの1つでクエリを試してみましたが、サブクエリへの結合として書き直してみました。

これははるかに速く機能しました、試してください!


はい、これはおそらくグループの結果を含む一時テーブルを作成するため、ビューバージョンと同じ速度になります。しかし、クエリプランは真実を伝える必要があります。
ypercubeᵀᴹ

3

これを試して

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

遅いSQLクエリをwww.prettysql.netで再フォーマットしました

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

クエリとサブクエリの両方でテーブルを使用する場合は、次のように常に両方にエイリアスを設定する必要があります。

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
 );

それは役に立ちますか?


1
残念ながら役に立ちません。実行速度は同じです。
quano 2011年

回答を更新しました。もう一度お試しいただけますか?group byが遅い場合でも、一度だけ実行する必要があります...
plang

前回、誤ってライブMySQLサーバーを強制終了したので、今はこれを試すことができません。後でテストデータベースをセットアップする必要があります。しかし、これがクエリに影響を与える理由がわかりません。HAVINGステートメントは、それが含まれているクエリにのみ適用する必要がありますね。「実際の」クエリがサブクエリに影響を与える理由が本当にわかりません。
quano 2011年

私はこれを見つけました:xaprb.com/blog/2006/04/30/…。これが解決策になると思います。時間があるときにやってみます。
quano 2011年

2

まず、重複する行を見つけて、行数が何回使用されているかを調べ、次のように番号順に並べます。

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
) 

その後、テーブルを作成し、それに結果を挿入します。

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
) 

最後に、重複行を削除します。いいえは開始0です。各グループの最初の数を除いて、すべての重複行を削除します。

delete from  CopyTable where No!= 0;


1

場合によっては、データが大きくなるとmysql WHERE INがクエリの最適化のためにかなり遅くなることがあります。STRAIGHT_JOINを使用して、クエリをそのまま実行するようにmysqlに指示してください。たとえば、

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

しかし注意してください:ほとんどの場合、mysqlオプティマイザーはかなりうまく機能するので、この種の問題がある場合にのみ使用することをお勧めします


0

これは、という名前のテーブルがある私の場合に似ていますtabel_buku_besar。私が必要なのは

  1. きたレコードを探しaccount_code='101.100'tabel_buku_besar持っているcompanyarea='20000'とも持っているIDRようcurrency

  2. tabel_buku_besarステップ1と同じaccount_codeを持つがtransaction_number、ステップ1の結果 を持つすべてのレコードを取得する必要があります

の使用select ... from...where....transaction_number in (select transaction_number from ....)中、クエリの実行が非常に遅くなり、リクエストがタイムアウトしたり、アプリケーションが応答しなくなったりする...

私はこの組み合わせと結果を試します...悪くない...

`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

これは、値が存在するかどうかを見つけるのに最も効率的であることがわかります。値が存在しない(つまり、IS NULL)かどうかを見つけるためにロジックを簡単に反転できます。

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を、テーブルに存在することを確認する値の名前に置き換えます

* primaryKeyを比較表の主キー列の名前に置き換えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.