新しいまたは変更されたデータに対してのみ、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
の更新が可能ですが、ビュー全体が更新されます。数億行の場合、これには数分かかります。
更新された値と新しい値を追跡し、ビューを部分的にのみ更新する良い方法は何ですか?
「マテリアライズドビュー」として機能する独自のテーブルをいつでも実装できます。 _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
はカバーしないことに注意してください。 。それをカバーするには、テーブル全体を考慮する必要があります。それはすべて正確な要件に依存します。
あなたが要求したような増分更新ではありませんが、Postgres 9.4は新しい concurrent update 機能を提供します。
ドキュメントを引用するには…
PostgreSQL 9.4より前は、マテリアライズドビューを更新すると、テーブル全体がロックされ、クエリが実行できなくなりました。排他ロックを取得するために更新に長い時間がかかる場合(それを使用するクエリが完了するのを待つ間)、順番に後続のクエリを保留しています。これはCONCURRENTLYキーワードで軽減できます。
postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;
ただし、マテリアライズドビューには一意のインデックスが存在する必要があります。マテリアライズドビューをロックする代わりに、一時的に更新されたバージョンを作成し、2つのバージョンを比較してから、マテリアライズドビューに対してINSERTとDELETEを適用して、違いを適用します。つまり、クエリは、更新中にマテリアライズドビューを使用できます。非並行形式とは異なり、タプルはフリーズされていません。前述のDELETEを実行すると、無効なタプルが残るため、VACUUMが必要です。
この同時更新は、まだ完全な新しいクエリを実行しています(増分ではありません)。したがって、CONCURRENTLYは全体的な計算時間を節約せず、更新中にマテリアライズドビューを使用できない時間を最小限に抑えます。