web-dev-qa-db-ja.com

PostgreSQLの増分マテリアライズドビューを更新する

新しいまたは変更されたデータに対してのみ、PostgreSQLでマテリアライズドビューを段階的に更新することは可能ですか?

このテーブルとマテリアライズドビューを考えてみましょう:

CREATE TABLE graph (
   xaxis integer NOT NULL,
   value integer NOT NULL,
);

CREATE MATERIALIZED VIEW graph_avg AS 
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis

定期的に、新しい値がgraphに追加されるか、既存の値が更新されます。ビューを更新したいgraph_avg更新された値についてのみ、数時間ごと。ただし、PostgreSQL 9.3では、テーブル全体が更新されます。これはかなり時間がかかります。次のバージョン9.4ではCONCURRENTの更新が可能ですが、ビュー全体が更新されます。数億行の場合、これには数分かかります。

更新された値と新しい値を追跡し、ビューを部分的にのみ更新する良い方法は何ですか?

35
user4150760

「マテリアライズドビュー」として機能する独自のテーブルをいつでも実装できます。 _MATERIALIZED VIEW_ がPostgres 9.3に実装される前は、このようにしました。

プレーンな VIEW を作成できます:

_CREATE VIEW graph_avg_view AS 
SELECT xaxis, AVG(value) AS avg_val
FROM   graph
GROUP  BY xaxis;
_

そして、一度または最初からやり直す必要があるときにいつでも結果を具体化します。

_CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view;
_

(または、SELECTを作成せずに、VIEWステートメントを直接使用します。)
次に、未使用のユースケースの詳細に応じて、DELETE/UPDATE/INSERTを手動で変更できます。

テーブルのデータ変更CTEを含む基本的なDMLステートメント

他に誰もwriteを_graph_avg_に同時に試行しないと仮定します(読み取りは問題ありません):

_WITH del AS (
   DELETE FROM graph_avg t
   WHERE  NOT EXISTS (SELECT FROM graph_avg_view WHERE xaxis = t.xaxis)
   )
, upd AS (
   UPDATE graph_avg t
   SET    avg_val = v.avg_val
   FROM   graph_avg_view v
   WHERE  t.xaxis = v.xaxis
   AND    t.avg_val <> v.avg_val
-- AND    t.avg_val IS DISTINCT FROM v.avg_val  -- alt if avg_val can be NULL
   )
INSERT INTO graph_avg t  -- no target list, whole row
SELECT v.*
FROM   graph_avg_view v
WHERE  NOT EXISTS (SELECT FROM graph_avg WHERE xaxis = v.xaxis);
_

基本レシピ

  • デフォルトのnow()を含むtimestamp列をベーステーブルに追加します。 ts。と呼びましょう。
    • 更新がある場合は、xaxisまたはvalueのいずれかを変更するすべての更新で現在のタイムスタンプを設定するトリガーを追加します。
  • 最新のスナップショットのタイムスタンプを記憶する小さなテーブルを作成します。 mvとしましょう:

    _CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
    _
  • この部分的な複数列のインデックスを作成します。

    _CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
    _
  • クエリの述語としてlastスナップショットのタイムスタンプを使用して、完全なインデックス使用でスナップショットを更新します。

  • トランザクションの最後に、インデックスを削除し、トランザクションタイムスタンプを使用して再作成します。これは、インデックス述語(最初は_'-infinity'_)のタイムスタンプを置き換え、テーブルにも保存します。 oneトランザクションのすべて。

  • 部分インデックスはINSERTおよびUPDATE操作をカバーするのに最適ですが、DELETEはカバーしないことに注意してください。 。それをカバーするには、テーブル全体を考慮する必要があります。それはすべて正確な要件に依存します。

23

同時更新(Postgres 9.4)

あなたが要求したような増分更新ではありませんが、Postgres 9.4は新しい concurrent update 機能を提供します。

ドキュメントを引用するには…

PostgreSQL 9.4より前は、マテリアライズドビューを更新すると、テーブル全体がロックされ、クエリが実行できなくなりました。排他ロックを取得するために更新に長い時間がかかる場合(それを使用するクエリが完了するのを待つ間)、順番に後続のクエリを保留しています。これはCONCURRENTLYキーワードで軽減できます。

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;

ただし、マテリアライズドビューには一意のインデックスが存在する必要があります。マテリアライズドビューをロックする代わりに、一時的に更新されたバージョンを作成し、2つのバージョンを比較してから、マテリアライズドビューに対してINSERTとDELETEを適用して、違いを適用します。つまり、クエリは、更新中にマテリアライズドビューを使用できます。非並行形式とは異なり、タプルはフリーズされていません。前述のDELETEを実行すると、無効なタプルが残るため、VACUUMが必要です。

この同時更新は、まだ完全な新しいクエリを実行しています(増分ではありません)。したがって、CONCURRENTLYは全体的な計算時間を節約せず、更新中にマテリアライズドビューを使用できない時間を最小限に抑えます。

15
Basil Bourque