1

これは私が本当に理解できない奇妙なことです。私はPostGreSql9.2で作業しています...

私はこのデータベースを持っています:

movies (id, title, votes)
infos (id, movie_id, info_type, value)

movie.votes を infos.value で更新し、movies.id = infos.movi​​e_if に参加し、info_type = 100 (これは投票のタイプです..)

私は2つの異なるクエリを試しました:

update movies
set votes = cast(i.value as integer)
from movies m inner join infos i on m.id = i.movie_id
where i.info_type = 100

これは (explain を使用して) 約 1100 万秒の実行時間を予測します (多すぎます!)

2回目の試行:

update movies m
set votes = cast(
(
  select value
  from infos i
  where i.info_type = 100 and i.movie_id = m.id
  limit 1
) AS integer);

これは「わずか」2万秒であるはずです..それでも多すぎます

クエリプランがどのように機能するのかよくわからないので、(active_record を使用して) ruby​​ スクリプトでこれを実行しようとしています...これは次のとおりです。

Info.find_in_batches(:conditions => "info_type = 100") do |group|
    group.each{ |info|
        movie = Movie.find(info.movie_id)
        movie.votes = info.value.to_i
        movie.save
    }
end

ruby を読んでいない人のために説明すると、このクエリは、info_type = 100 の条件を満たすすべての情報を単純にループし、それぞれの情報について、対応するムービーを検索して更新します。

そして、それは非常に速かったです!ほんの数分で、すべてのルビー/オームのオーバーヘッドが発生します!!

さて、なぜ?? 映画には約 600k のレコードがあることを知っていますが、200k (3 分の 1) だけが投票数の情報レコードを持っています。それでも、これは何が起こっているのかを説明していません。

4

2 に答える 2

2

EXPLAIN

@ruakhがすでに説明したように、あなたはおそらくあなたEXPLAINに言っていることを誤解しています. 秒単位の実際の時間が必要な場合は、 を使用しますEXPLAIN ANALYZE

ただし、これは実際にステートメントを実行することに注意してください。ここでマニュアルを引用します:

ANALYZE 重要:オプションを使用すると、ステートメントが実際に実行されることに注意してください。EXPLAIN は SELECT が返す出力をすべて破棄しますが、ステートメントのその他の副作用は通常どおり発生します。コマンドがデータに影響を与えずに、、、、、またはステートメントで使用する場合は、次のアプローチを使用EXPLAIN ANALYZEしますINSERTUPDATEDELETECREATE TABLE ASEXECUTE

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

それでも、最初のクエリの推定値は高く、重大な問題を示しています。

どうしたの?

3番目のアプローチについて:大きなテーブルの場合、データベースサーバーにすべての行のサーバーに指示を送信するよりも、一度に(大きな)テーブル全体を更新させる方が常に1桁速くなります。値はデータベース内から取得されます。この関連する回答の詳細。テストがそうでない場合は、(テスト) セットアップに何か問題がある可能性があります。そして実際、それは...

最初のクエリは完全に失敗します。とんでもないパフォーマンスの見積もりは、それがいかにひどく間違っているかを示しています。節でテーブルmoviesをテーブルinfosに結合するときに、結果の行をテーブルの行にバインドする条件をFROM忘れています。つまり、( 600k ) のすべての行が(200k)のすべての投票で更新され、結果として120 000 000 000 の更新が行われます。うまい。そして、すべてが間違っています。絶対に実行しないでください。ロールバックできるトランザクションでさえありません。WHEREUPDATECROSS JOINmoviesvalues

2番目のクエリも失敗します相関サブクエリを実行します。つまり、行ごとに個別のクエリを実行します。これは 1 つではなく 600k のサブクエリであるため、ひどいパフォーマンスになります。

まさに 600k サブクエリです。200kではありません。Postgres にすべてのムービーを更新するように指示します。一致しないものinfos.value( no ) は、 で値をinfo_type = 100受け取り、以前にあったものを上書きします。NULLvotes

また、それLIMIT 1はそこで何をしているのだろうか?

  • どちらか(infos.movie_id, infos.info_type)UNIQUEである場合、 は必要ありませんLIMIT
  • または、そうではありませんUNIQUE。次に、構造を保持する場合はUNIQUE indexto を追加します。infos

適切なクエリ

UPDATE movies m
SET    votes = i.value::int
FROM   infos i
WHERE  m.id = i.movie_id
AND    i.info_type = 100
AND    m.votes IS DISTINCT FROM i.value::int;

これは最初のクエリによく似ていますが、単純化して適切に実行するだけでなく、拡張機能も追加されています。

  • 2回目以降のご参加は不要moviesです。節infosでのみ必要です。FROM

  • 更新する行を実際に新しい値を持つ行にバインドし、(意図しない) を回避しますCROSS JOIN

    WHERE  m.id = i.movie_id
    
  • 空の更新は避けてください。無駄なコストがかかります。それが最後の行です。

数百万秒ではなく、数秒以下にする必要があります。
ところで、インデックスはこのクエリには役立ちません。関連するテーブルのすべて (または 3 分の 1) を使用するため、記述されたデータ分散のテーブル スキャンの方が高速です。

于 2012-10-05T21:06:55.420 に答える
1

[…] これは (explain を使用して) 約 1100 万秒の実行時間を予測します (長すぎます!)

[…] これは「たった」20,000秒だろう..それでも多すぎる

の出力を誤解していると思いますEXPLAINそのドキュメントで説明されているように、「推定ステートメント実行コスト」(つまり、「ステートメントの実行にかかる時間に関するプランナーの推測」) は秒単位ではなく、「任意のコスト単位で測定されますが、従来はディスクを意味します」ページ フェッチ".

したがって、PostgreSQL は、2 番目のステートメントが最初のステートメントよりも約 500 倍速く実行されると推測していますが、どちらもあなたが考えるほど長くはかかりません。:-)

于 2012-10-05T19:30:16.130 に答える