Postgres UPDATEに39時間かかったのはなぜですか?


17

最大210万行のPostgresテーブルがあります。私はそれについて以下の更新を実行しました:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

このクエリの実行には39時間かかりました。私はこれを4(物理)コアi7 Q720ラップトッププロセッサ、大量のRAMで実行していますが、他のほとんどはほとんど実行していません。HDDスペースの制限はありません。テーブルは最近、バキュームされ、分析され、インデックスが再作成されました。

少なくとも最初のWITH完了後、クエリが実行されている間は常に、CPU使用率は通常低く、HDDは100%使用されていました。HDDが非常に激しく使用されていたため、他のアプリの実行速度は通常よりもかなり遅くなりました。

ラップトップの電源設定は高パフォーマンス(Windows 7 x64)でした。

ここに説明があります:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1数万行のみを除外します。そのWHERE条項があっても、私はまだ200万行以上を操作しています。

ハードドライブは、TrueCrypt 7.1aでドライブ全体が暗号化されています。それは少し物事を遅くしますが、クエリがその多くの時間かかるようにするのに十分ではありません。

WITH一部のみを実行するために約3分かかります。

arrest_idフィールドには、外部キーにはインデックスがありませんでした。このテーブルには8つのインデックスと2つの外部キーがあります。クエリの他のすべてのフィールドにはインデックスが付けられます。

arrest_idフィールドは除いては制約がありませんでしたNOT NULL

テーブルには合計32列があります。

arrest_id型は、文字Variable(20)です。私rank()は数値を生成することを認識していますが、このフィールドに非数値データを使用する他の行があるため、文字を変化させる(20)citing_jurisdiction<>1を使用する必要があります。

arrest_idすべての行のフィールドは空白でしたciting_jurisdiction=1

これは個人用のハイエンド(1年前)のラップトップです。私は唯一のユーザーです。他のクエリまたは操作は実行されていません。ロックはありそうもない。

このテーブルまたはデータベースのどこにもトリガーはありません。

このデータベースに対する他の操作に時間がかかることはありません。適切なインデックスを使用すると、SELECTクエリは通常非常に高速です。


それらSeq Scanは少し怖いです
...-rogerdpack

回答:


18

350万行のテーブルで最近似たようなことが起こりました。私の更新は決して終わりません。多くの実験とフラストレーションの後、私は最終的に犯人を見つけました。更新されるテーブルのインデックスであることが判明しました。

解決策は、更新ステートメントを実行する前に、更新されるテーブルのすべてのインデックスを削除することでした。それをやったら、数分でアップデートが終了しました。更新が完了したら、インデックスを再作成し、業務に戻りました。これはおそらく現時点では役に立ちませんが、他の誰かが答えを探しているかもしれません。

データをプルするテーブルのインデックスを保持します。インデックスを更新し続ける必要はなく、更新するデータを見つけるのに役立ちます。遅いラップトップでは問題なく動作しました。


3
私はあなたへのベストアンサーを切り替えています。これを投稿してから、更新される列に既に値があり、インデックスがない(!)場合でも、インデックスが問題になる他の状況に遭遇しました。Postgresには、他の列のインデックスの管理方法に問題があるようです。テーブルに対する唯一の変更がインデックスのない列を更新することであり、その列の行に割り当てられた領域を増やしていない場合、これらの他のインデックスが更新のクエリ時間を膨らませる理由はありません。
アレンキャンブル14

1
ありがとう!それが他の人を助けることを願っています。一見非常に単純なことで頭痛の種を何時間も省くことができたでしょう。
JCアベナ14

5
@ArenCambre-理由があります:PostgreSQLは行全体を別の場所にコピーし、古いバージョンを削除済みとしてマークします。これは、PostgreSQLがマルチバージョン同時実行制御(MVCC)を実装する方法です。
ピョートル・フィンデイセン

私の質問は...なぜ犯人なのですか?stackoverflow.com/a/35660593/32453
rogerdpack

15

あなたの最大の問題は、ラップトップのハードドライブで大量の書き込み、シークを大量に行うことです。特に、多くのラップトップに搭載されている低速の5400RPMドライブの場合は、何をしても高速になることはありません。

TrueCryptは、書き込みを「少し」遅くします。読み取りはかなり高速になりますが、書き込みによりRAID 5は高速になります。TrueCryptボリュームでDBを実行すると、書き込み、特にランダム書き込みに対する拷問になります。

この場合、クエリを最適化しようとして時間を無駄にしていると思います。とにかくほとんどの行を書き換えていますが、恐ろしい書き込み状況では遅くなります。私がお勧めするのは:

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

UPDATEにはかなりランダムな書き込みパターンが含まれるため、制約のみをドロップして再作成するよりも高速になると思います 殺すあなたのストレージを。ロギングなしのテーブルへの一括挿入と、制約なしのWALでログされたテーブルへの一括挿入は、おそらくより高速になります。

絶対に最新のバックアップがあり、バックアップからデータベースを復元する必要がない場合は、fsync=offパラメータを使用して一時的に PostgreSQLを再起動し、full_page_writes=off 一時的に、このバルク操作のために。停電やOSクラッシュなどの予期しない問題が発生すると、データベースは回復不能になりますfsync=off

「ロギングなし」に相当するPOSTGreSQLは、ロギングされていないテーブルを使用することです。これらのログに記録されていないテーブルは、ダーティな状態でDBがクリーンにシャットダウンすると切り捨てられます。ログに記録されていないテーブルを使用すると、書き込みの負荷が少なくとも半分になり、シークの回数が減るため、LOTを高速化できます。

Oracleの場合と同様に、大きなバッチ更新の後にインデックスを削除して再作成することをお勧めします。PostgreSQLのプランナーは、大きな更新が行われていると判断できず、インデックスの更新を一時停止し、最後にインデックスを再構築します。それができたとしても、どの時点でこれを行う価値があるのか​​、特に事前に判断するのは非常に難しいでしょう。


この答えは、大量の書き込みと、ひどい暗号化と遅いラップトップドライブのパフォーマンスにスポットを当てています。私は8つのインデックスの存在は、多くの余分な書き込みと敗北適用生成することにも注意だろうホットインデックスを滴下し、下部用いて、ブロック内の行の更新フィルファクタを表にすると、行の移行のトンを防止することができる
dbenhur

1
FILLFACTORでHOTの可能性を高めることをお勧めします-ただし、TrueCryptを使用すると、巨大なブロックでブロックの読み取り/書き換えサイクルが強制されるため、それが大いに役立つかはわかりません。テーブルの成長は少なくとも線形のブロックの書き込みを行うため、行の移行はさらに高速になる可能性があります。
クレイグリンガー

2.5年後、私は同じようなことをしていますが、より大きなテーブルで行っています。念のため、更新する単一の列にインデックスが付けられていなくても、すべてのインデックスを削除することをお勧めしますか?
アレンキャンブル14

1
@ArenCambreその場合...まあ、それは複雑です。更新のほとんどが対象となる場合HOTは、インデックスをそのままにしておくことをお勧めします。そうでない場合は、おそらくドロップして再作成する必要があります。列にはインデックスが付けられていませんが、HOT更新を行うには、同じページに空き領域が必要であるため、テーブル内のデッドスペースの量に少し依存します。書き込みがほとんどの場合、すべてのインデックスを削除します。大量に更新されている場合は、穴が開いている可能性があり、大丈夫かもしれません。などのツールはpageinspectpg_freespacemapこれを決定するのに役立ちます。
クレイグリンガー14

ありがとう。この場合、すべての行へのエントリがすでにあるブール列です。いくつかの行のエントリを変更していました。確認したところですが、すべてのインデックスを削除してから2時間で更新が完了しました。事前に、18時間後に更新を停止する必要がありました。これは、更新されていた列が確実にインデックス付けされていなかったという事実にもかかわらずです。
アレンカンブレ14

2

誰かがPostgresに対してより良い答えを与えますが、ここではOracleの観点から見たいくつかの所見が当てはまります(そしてコメントはコメントフィールドに対して長すぎます)。

私の最初の懸念は、1つのトランザクションで200万行を更新しようとすることです。Oracleでは、更新される各ブロックの変更前イメージを作成するため、変更されたブロックを読み取らずに他のセッションが一貫した読み取りを保持し、ロールバックすることができます。これは長いロールバックが蓄積されることです。通常、トランザクションは小さなチャンクで行うほうがよいでしょう。一度に1,000レコードを言います。

テーブルにインデックスがあり、メンテナンス中にテーブルが動作していないと見なされる場合は、大規模な操作の前にインデックスを削除してから、再作成する方がよい場合がよくあります。安価で、更新されたレコードごとにインデックスを常に維持しようとしています。

Oracleでは、ジャーナリングを停止するステートメントの「ロギングなし」ヒントを許可しています。ステートメントを高速化しますが、データベースを「回復不能」な状態のままにします。したがって、前にバックアップし、その後すぐに再度バックアップする必要があります。Postgresに同様のオプションがあるかどうかはわかりません。


PostgreSQLには長いロールバックの問題はなく、存在しません。ROLBACKは、トランザクションの大きさに関係なく、PostgreSQLで非常に高速です。オラクル= PostgreSQLの!
フランクHeikens

@FrankHeikensありがとう、それは面白い。ジャーナリングがPostgresでどのように機能するかについて調べる必要があります。トランザクションの概念全体を機能させるためには、トランザクション中に何らかの方法で2つの異なるバージョンのデータ、変更前イメージと変更後イメージを維持する必要があり、それが私が参照しているメカニズムです。何らかの方法で、トランザクションを維持するためのリソースが高すぎるというしきい値を超えると思います。
グレン

2
@Glenn postgresはテーブル自体に行のバージョンを保持します- 説明についてはこちらをご覧ください。妥協点は、「デッド」タプルがぶら下がることです。これは、postgresで「バキューム」と呼ばれるものと非同期にクリーンアップされます(テーブル自体に「デッド」行がないため、バキュームは必要ありません)
ジャック・ダグラス

どういたしまして:サイトへようこそ:
ジャックダグラス

@Glenn PostgreSQLの行バージョンの同時実行制御に関する標準的なドキュメントはpostgresql.org/docs/current/static/mvcc-intro.htmlであり、読む価値があります。wiki.postgresql.org/wiki/MVCCも参照してください。デッドロウのあるMVCC VACUUMは答えの半分にすぎないことに注意してください。PostgreSQLは、いわゆる「先読みログ」(実質的にジャーナル)を使用して、アトミックコミットを提供し、部分的な書き込みなどから保護します。postgresql.org/ docs / current / static / wal
Craig Ringer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.