列行
... 操作全体でトランザクションの整合性を維持する必要はありません。変更する列が更新中に書き込まれたり読み取られたりしないことがわかっているからです。
PostgreSQL の MVCC モデルのAnyUPDATE
は、行全体の新しいバージョンを書き込みます。同時トランザクションが同じ行のいずれかの列を変更すると、時間のかかる同時実行の問題が発生します。詳細はマニュアルにて。同じ列が同時トランザクションによって処理されないことがわかっていると、いくつかの複雑な問題を回避できますが、他の問題は回避できません。
索引
話題から外れた議論に転用されるのを避けるために、3500 万の列の status のすべての値が現在同じ (null 以外の) 値に設定されているため、インデックスが役に立たないと仮定します。
テーブル全体(またはその主要部分) を更新する場合、 Postgresは index を使用しません。すべてまたはほとんどの行を読み取る必要がある場合は、順次スキャンの方が高速です。逆に: インデックスのメンテナンスは、 の追加コストを意味しUPDATE
ます。
パフォーマンス
たとえば、3,500 万行の "orders" というテーブルがあり、次のようにしたいとします。
UPDATE orders SET status = null;
より一般的な解決策を目指していることは承知しています(以下を参照)。しかし、実際に尋ねられた質問に対処するには、テーブルのサイズに関係なく、ミリ秒単位で処理できます。
ALTER TABLE orders DROP column status
, ADD column status text;
マニュアル (Postgres 10 まで):
で列を追加するとADD COLUMN
、テーブル内のすべての既存の行が列のデフォルト値で初期化されます (
句が指定されNULL
ていない場合)。DEFAULT
句がないDEFAULT
場合、これは単なるメタデータの変更です [...]
マニュアル (Postgres 11 以降):
列が追加されADD COLUMN
、非揮発性DEFAULT
が指定されている場合、デフォルトはステートメントの時点で評価され、結果はテーブルのメタデータに格納されます。その値は、既存のすべての行の列に使用されます。noDEFAULT
を指定すると、NULL が使用されます。どちらの場合も、テーブルの書き換えは必要ありません。
volatile を持つ列を追加しDEFAULT
たり、既存の列の型を変更したりするには、テーブル全体とそのインデックスを書き換える必要があります。[...]
と:
フォームは列を物理的に削除するのDROP COLUMN
ではなく、単に SQL 操作から見えなくするだけです。テーブルでの後続の挿入操作と更新操作では、列に null 値が格納されます。したがって、列を削除するのは簡単ですが、削除された列が占有していた領域が再利用されないため、テーブルのディスク上のサイズがすぐに減少するわけではありません。既存の行が更新されると、時間の経過とともにスペースが再利用されます。
列に依存するオブジェクト (外部キー制約、インデックス、ビューなど) がないことを確認してください。それらを削除/再作成する必要があります。それを除けば、システム カタログ テーブルに対する小さな操作が機能しますpg_attribute
。テーブルに排他ロックが必要です。これは、同時負荷が大きい場合に問題になる可能性があります。(Buurman が彼のコメントで強調しているように。) それを除いて、操作は数ミリ秒の問題です。
保持したい列のデフォルトがある場合は、別のコマンドで追加し直します。同じコマンドで実行すると、すぐにすべての行に適用されます。見る:
デフォルトを実際に適用するには、バッチで行うことを検討してください。
一般的な解決策
dblink
別の回答で言及されています。これにより、暗黙的な個別の接続で「リモート」の Postgres データベースにアクセスできます。「リモート」データベースを現在のデータベースにすることができるため、「自律的なトランザクション」を実現できます。関数が「リモート」データベースに書き込む内容はコミットされ、ロールバックできません。
これにより、大きなテーブルを小さな部分で更新する単一の関数を実行でき、各部分は個別にコミットされます。行数が非常に多い場合にトランザクションのオーバーヘッドが増大するのを回避し、さらに重要なことに、各部分の後にロックを解放します。これにより、同時操作を大幅な遅延なしで進めることができ、デッドロックの可能性が低くなります。
同時アクセスがない場合、これはほとんど役に立ちません-ROLLBACK
例外の後を避けることを除いて。SAVEPOINT
その場合もご検討ください。
免責事項
まず第一に、多くの小さなトランザクションは実際にはより高価です。これは、大きなテーブルの場合にのみ意味があります。スイート スポットは多くの要因に依存します。
何をしているのかよくわからない場合:単一のトランザクションが安全な方法です。これが適切に機能するためには、テーブルでの同時操作がうまく機能する必要があります。たとえば、同時書き込みは、すでに処理されているはずのパーティションに行を移動できます。または、同時読み取りでは、一貫性のない中間状態が表示される可能性があります。あなたは警告されました。
ステップバイステップの説明
追加モジュール dblink を最初にインストールする必要があります。
dblink を使用した接続の設定は、DB クラスターの設定と適用されているセキュリティ ポリシーに大きく依存します。それは難しいかもしれません。dblink に接続する方法の詳細については、後で関連する回答を参照してください。
FOREIGN SERVER
そこの指示に従って と を作成してUSER MAPPING
、接続を簡素化および合理化します (まだ作成していない場合)。
いくつserial PRIMARY KEY
かのギャップがある場合とない場合があります。
CREATE OR REPLACE FUNCTION f_update_in_steps()
RETURNS void AS
$func$
DECLARE
_step int; -- size of step
_cur int; -- current ID (starting with minimum)
_max int; -- maximum ID
BEGIN
SELECT INTO _cur, _max min(order_id), max(order_id) FROM orders;
-- 100 slices (steps) hard coded
_step := ((_max - _cur) / 100) + 1; -- rounded, possibly a bit too small
-- +1 to avoid endless loop for 0
PERFORM dblink_connect('myserver'); -- your foreign server as instructed above
FOR i IN 0..200 LOOP -- 200 >> 100 to make sure we exceed _max
PERFORM dblink_exec(
$$UPDATE public.orders
SET status = 'foo'
WHERE order_id >= $$ || _cur || $$
AND order_id < $$ || _cur + _step || $$
AND status IS DISTINCT FROM 'foo'$$); -- avoid empty update
_cur := _cur + _step;
EXIT WHEN _cur > _max; -- stop when done (never loop till 200)
END LOOP;
PERFORM dblink_disconnect();
END
$func$ LANGUAGE plpgsql;
電話:
SELECT f_update_in_steps();
必要に応じて任意の部分をパラメータ化できます: テーブル名、列名、値など... SQL インジェクションを避けるために識別子を必ずサニタイズしてください:
空の UPDATE を避ける: