web-dev-qa-db-ja.com

MySQLが2つの大きなテーブルを結合するのが非常に遅い

2つのテーブルがあります。1つにはダウンロードしたURLの履歴が含まれ、もう1つのテーブルには各URLの詳細が含まれています。

次のクエリは、過去1時間の繰り返し回数でURLをグループ化します。

SELECT COUNT(history.url) as total, history.url
FROM history
WHERE history.time > UNIX_TIMESTAMP()-3600
GROUP BY history.url
ORDER BY COUNT(history.url) DESC 
LIMIT 30

上記のクエリの実行には約800msかかりますが、十分な速度ではありませんが、許容範囲ですが、

ただし、キャッシュテーブルと結合すると、新しいクエリの実行に約25秒かかり、非常に遅くなります。

SELECT th.total, th.url, tc.url, tc.json 
FROM (SELECT COUNT(history.url) as total, history.url
      FROM history 
      WHERE history.time > UNIX_TIMESTAMP()-3600
      GROUP BY history.url
      ORDER BY COUNT(history.url) DESC 
      LIMIT 30
) th
INNER JOIN (SELECT cache.url, cache.json FROM cache) tc
    ON th.url = tc.url
GROUP BY th.url
ORDER BY th.total DESC
LIMIT 30

「tc」では、キャッシュテーブル全体がロードされており、100万以上のエントリが含まれているため、これが発生している可能性があると思います。

最初のクエリを使用し、プログラムで結果を反復処理してから、結果ごとにキャッシュからSELECTクエリを実行すると、はるかに高速になります。とにかく2番目のクエリを高速化する方法はありますか?

追伸InnoDBを使用しています

UPDATE EXPLAINを使用した2番目のクエリの出力 enter image description here

「履歴」テーブルの構造 enter image description here

「キャッシュ」テーブルの構造 enter image description here

1
Ali AlNoaimi

一般に、JOIN述語またはWHERE句に参加する列にインデックスを付けることをお勧めします。よくある間違いは、複数列のインデックスを減らすのではなく、1列のインデックスをいくつか作成することです。ここでは、URLと履歴の時間の両方からメリットを得ることができます(これらのテーブルに対するすべてのクエリを確認すると、これらのインデックスに列を追加できることがわかります)。

CREATE INDEX x01_history_url ON HISTORY (URL, TIME);
CREATE INDEX x01_cache_url ON CACHE (URL);

次に、クエリのネストを解除してみます。 MySQLは、実行できるクエリ書き換えの種類に制限があるため、ネストにより不要なオーバーヘッドが発生する可能性があります。

SELECT COUNT(th.url) as total, tc.url, tc.json 
FROM history th
JOIN cache tc
    ON th.url = tc.url
WHERE th.time > UNIX_TIMESTAMP()-3600
GROUP BY tc.url, tc.json
ORDER BY COUNT(th.url) DESC 
LIMIT 30

このクエリは意味的にクエリとは異なるため、異なる結果が得られる可能性があります。これが問題である場合は、以前と同じようにサブクエリでLIMIT 30構成を維持することをお勧めします。同様の制限をCACHEに追加できるかどうかを検討することもできます。合計30行を取得するために調査する必要があるCACHE行の数に上限はありますか?

INNER JOIN (SELECT cache.url, cache.json 
            FROM cache
            ORDER BY ? LIMIT ?) tc
4
Lennart

最初のクエリの場合:

_SELECT COUNT(*) as total,  -- * is the common pattern
       url
    FROM history
    WHERE time > NOW() - INTERVAL 1 HOUR
    GROUP BY url
    ORDER BY COUNT(*) DESC 
    LIMIT 30

INDEX(time, url)  -- in this order
INDEX(url, time)  -- maybe this order
_

両方のインデックスを使用します。 MySQLのバージョンや時間範囲が異なれば、一方のインデックスともう一方のインデックスを使用する場合があります。

2番目のクエリでは、派生テーブルを不必要に使用しないでください。

_INNER JOIN (SELECT cache.url, cache.json FROM cache) tc
   ON th.url = tc.url
_

->

_INNER JOIN cache tc
    ON th.url = tc.url
_

そしてcacheにはINDEX(url)が必要です。

2つのテーブルは1:1または1:多数または多数:1または多数:多数ですか?外側の_GROUP/ORDER/LIMIT_に違いが生じる場合があります。

その間、テーブルに_SHOW CREATE TABLE_を指定してください。

1
Rick James