web-dev-qa-db-ja.com

MySQLの「IN」クエリはサブクエリでは非常に遅くなりますが、明示的な値では速くなります

MySQLクエリがあります(Ubu 10.04、Innodb、Core i7、16Gb RAM、SSDドライブ、MySQLパラメータが最適化されています):

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (SELECT l.id FROM em_link l WHERE l.campaignid = '2900' AND l.link != 'open')

テーブルem_link_dataには約700万行、em_linkには数千行あります。このクエリは、完了するまでに約18秒かかります。ただし、サブクエリの結果を置き換えてこれを行うと、次のようになります。

SELECT
COUNT(DISTINCT subscriberid)
FROM
em_link_data
WHERE
linkid in (24899,24900,24901,24902);

その後、クエリは1ミリ秒未満で実行されます。サブクエリだけで1ミリ秒未満で実行され、列linkidにインデックスが付けられます。

クエリを結合として書き直すと、1ミリ秒未満になります。 「IN」クエリにサブクエリが含まれていると非常に遅いのはなぜですか。また、値が含まれているとなぜ非常に高速なのですか。クエリ(購入したソフトウェア)を書き直すことができないので、このクエリを高速化するための微調整やヒントがあることを期待していました!どんな助けでも大歓迎です。

21
Franco

サブクエリは、評価するたびに実行されます(MySQLでは、すべてのRDBMSではありません)。つまり、基本的に700万のクエリを実行しています。可能であれば、JOINを使用すると、これが1に減少します。インデックスを追加するとパフォーマンスが向上しますが、実行中です。

23
Brian

はい、サブクエリを使用したINは低速です。代わりに結合を使用してください。

SELECT
COUNT(DISTINCT subscriberid)
FROM em_link_data JOIN em_link ON em_link_data.linkid=em_link.id
WHERE em_link.campaignid = '2900' AND em_link.link != 'open'

また、em_link_data.linkidem_link.idにインデックスを定義していることを確認してください。

5
awm

問題は、MySQLが外部から内部にクエリを実行する一方で、サブクエリが1回実行された後、その結果が外部クエリのWHERE式に渡されることです( MySQLドキュメント を参照)。

クエリを書き直すことができない場合は、次の最適化を行う必要があります。

  • frustratedWithFormsDesignerが言ったように、campaignidlinkにインデックスを追加します
  • EXPLAIN SELECT ...を実行して、サブクエリがインデックスを正しく使用していることを確認します
  • クエリキャッシュを有効にして微調整します。これにより、サブクエリが複数回呼び出される速度が向上します。

もう1つのアイデアは、 MySQLプロキシ をインストールし、クエリをインターセプトして結合を使用するように書き換える小さなスクリプトを作成することです。

4
Alessandro

サブクエリが高速である場合、campaignidとlinkは完全にインデックス付けされます。 l.idはPKであり、クラスター化されているため高速です。しかし、私が覚えている限り(前回この件名をチェックしたときから)、mysqlは、パフォーマンスを向上させるためにインデックスソートのサブクエリ結果を使用するための「in」サブクエリの内部最適化について説明し、「IN」の左側にもキャッシュを使用しますサブクエリ内にすばやくドラッグし、インデックスがtrueに設定されている場合は、キャッシュではなく内部結合または「IN」を使用してもそのような違いがあってはなりません。キャッシュの問題と大量のデータが原因である可能性があります。 http://dev.mysql.com/doc/internals/en/transformation-scalar-in.html

ソフトウェアの状況はわかりませんが、INNER JOINを使用でき、外部クエリのWHERE句のIN句の前に(おそらく)いくつかの追加の定義がある場合は、その句をメインのINNERの前に移動してください。一時的なINNERJOINを介したJOINは、便利な「where」句と同様に順次動作し、次のようにJOINの相互比較の数を減らします。

SELECT ... FROM t
INNER JOIN (SELECT 1) AS tmp ON t.asd=23
INNER JOIN t2 ON ...

通常の結合ルックアップと一時的な結合ルックアップのサンプル比較:1000 * 1000> 1000 +(100 * 1000)

また、サブクエリは定数値でフィルタリングされているようです。したがって、私であれば、結果セットを生成するサブクエリに句を配置し、次のようにJOINでの比較の数を減らします。

SELECT ... FROM t
INNER JOIN (SELECT ... FROM t2 WHERE constant clauses) AS tbl2 ON ...

とにかく、「IN」クエリでは、サブクエリのテーブルの任意の列を外部クエリのテーブルの任意の列と比較するには、両側の列に正確にインデックスを付ける必要があります(複合インデックスに関して)が、それでもキャッシュである可能性があります問題。

編集済み:また、私は質問したいと思いました:l.campaignid、l.link、およびl.idで複合インデックスを作成することには意味がありますか?

0
Alix