sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQLで大規模な非ブロッキング更新を行うにはどうすればよいですか?

    列/行

    ...変更する列が更新中に書き込まれたり読み取られたりしないことがわかっているため、操作全体でトランザクションの整合性を維持する必要はありません。

    UPDATE PostgreSQLのMVCCモデルでは、行全体の新しいバージョンが書き込まれます 。同時トランザクションが変更された場合any 同じ行の列では、時間のかかる同時実行の問題が発生します。マニュアルの詳細。同じを知っている 同時トランザクションに影響されない一部を回避する 起こりうる合併症ですが、他の合併症はありません。

    インデックス

    トピック外の議論に流用されるのを避けるために、3500万列のステータスのすべての値が現在同じ(null以外の)値に設定されていると仮定して、インデックスを役に立たなくします。

    テーブル全体を更新する場合 (またはその主要部分)Postgresインデックスを使用しない 。すべてまたはほとんどの行を読み取る必要がある場合は、順次スキャンの方が高速です。それどころか、インデックスのメンテナンスは、UPDATEの追加コストを意味します 。

    パフォーマンス

    たとえば、3500万行の「注文」というテーブルがあり、これを実行したいとします。

    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 が指定されている場合、デフォルトはステートメントの時点で評価され、結果はテーブルのメタデータに格納されます。その値は、既存のすべての行の列に使用されます。 DEFAULTがない場合 指定すると、NULLが使用されます。どちらの場合も、テーブルの書き換えは必要ありません。

    揮発性のDEFAULTを含む列を追加する または、既存の列のタイプを変更すると、テーブル全体とそのインデックスを書き換える必要があります。 [...]

    そして:

    DROP COLUMN formは列を物理的に削除しませんが、SQL操作からは見えないようにします。後続のテーブルへの挿入および更新操作では、列のnull値が格納されます。したがって、列の削除は迅速ですが、削除された列が占めるスペースが再利用されないため、テーブルのディスク上のサイズがすぐに縮小されることはありません。スペースは、既存の行が更新されると、時間の経過とともに再利用されます。

    列に依存するオブジェクト(外部キー制約、インデックス、ビューなど)がないことを確認してください。それらを削除/再作成する必要があります。それを除けば、システムカタログテーブルpg_attributeに対する小さな操作 仕事をしなさい。 排他的ロックが必要です テーブル上で、同時負荷が大きい場合に問題になる可能性があります。 (Buurmanがコメントで強調しているように。)それを除けば、操作はミリ秒の問題です。

    保持したい列のデフォルトがある場合は、別のコマンドで追加し直します。 。同じコマンドで実行すると、すべての行にすぐに適用されます。参照:

    • テーブルロックなしで新しい列を追加しますか?

    デフォルトを実際に適用するには、バッチで実行することを検討してください。

    • PostgreSQLは、NULL以外のデフォルトの列の追加を最適化しますか?

    一般的な解決策

    dblink 別の回答で言及されています。これにより、暗黙の個別接続で「リモート」Postgresデータベースにアクセスできます。 「リモート」データベースを現在のデータベースにすることで、「自律トランザクション」を実現できます。 :関数が「リモート」データベースに書き込むものはコミットされており、ロールバックできません。

    これにより、大きなテーブルを小さな部分で更新する単一の関数を実行でき、各部分は個別にコミットされます。非常に多数の行のトランザクションオーバーヘッドの蓄積を回避し、さらに重要なことに、各部分の後にロックを解放します。これにより、同時操作をそれほど遅れることなく進めることができ、デッドロックが発生する可能性が低くなります。

    同時アクセスがない場合、これはほとんど役に立ちません-ROLLBACKを回避する場合を除きます 例外の後。 SAVEPOINTも検討してください その場合。

    免責事項

    まず第一に、多くの小さなトランザクションは実際にはより高価です。この大きなテーブルにのみ意味があります 。スイートスポットは多くの要因に依存します。

    何をしているのかわからない場合:単一のトランザクションが安全な方法です 。これが正しく機能するためには、テーブルでの同時操作が一緒に行われる必要があります。例:同時書き込み すでに処理されていると思われるパーティションに行を移動できます。または、同時読み取りで一貫性のない中間状態が表示される場合があります。 警告されました。

    ステップバイステップの説明

    追加のモジュールdblinkを最初にインストールする必要があります:

    • PostgreSQLでdblinkを使用(インストール)する方法は?

    dblinkとの接続の設定は、DBクラスターの設定と適切なセキュリティポリシーに大きく依存します。注意が必要な場合があります。 dblinkに接続する方法に関連する後の回答 :

    • 関数が中止されてもUDFに永続的に挿入されます

    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インジェクションを回避するために、必ず識別子をサニタイズしてください。

    • PostgreSQL関数パラメータとしてのテーブル名

    空の更新を避けてください:

    • 複数の列でDISTINCTを選択するにはどうすればよいですか(またはできますか?


    1. 大規模なIMDBモデルを作成しようとしたときの失敗のトラブルシューティング

    2. SQLiteOpenHelper onCreateメソッドが呼び出されたとき?

    3. MySQLクエリをスケジュールする方法は?

    4. OracleのLISTAGG()関数