4

PostgreSQL にテーブル (実際には複数のテーブルがありますが、簡単にするために 1 つだけと仮定します) と、変更された項目を見つけるために定期的にテーブルにクエリを実行する必要がある複数のクライアントがあります。これらは更新または挿入されたアイテムです (削除されたアイテムは、最初に削除のマークを付けてから、猶予期間後に実際に削除することによって処理されます)。

明らかな解決策は、各行の「変更された」タイムスタンプ列を保持し、各クライアントごとにそれを覚えてから、変更されたものを単純にフェッチすることです

SELECT * FROM the_table WHERE modified > saved_modified_timestamp;

変更された列は、次のようなトリガーを使用して最新の状態に保たれます。

CREATE FUNCTION update_timestamp()
    RETURNS trigger
    LANGUAGE ‘plpgsql’
AS $$
    BEGIN
        NEW.modified = NOW();
        RETURN NEW;
    END;
$$;

CREATE TRIGGER update_timestamp_update
    BEFORE UPDATE ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_timestamp();

CREATE TRIGGER update_timestamp_insert
    BEFORE INSERT ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_timestamp();

ここで明らかな問題はNOW()、トランザクションが開始された時間です。そのため、更新された行のフェッチ中にトランザクションがまだコミットされておらず、トランザクションがコミットされたときに、タイムスタンプが saved_modified_timestamp よりも小さいため、更新が登録されないことがあります。

うまくいく解決策を見つけたと思います。このアプローチで欠陥を見つけることができるかどうかを確認したかったのです。

基本的な考え方は、タイムスタンプの代わりに xmin (またはむしろtxid_current()) を使用し、変更を取得するときに明示的なトランザクションでそれらをラップし、トランザクションから(または含まれる 3 つの値を)REPEATABLE READ読み取ることです。txid_snapshot()txid_snapshot_xmin()txid_snapshot_xmax()txid_snapshot_xip()

postgres のドキュメントを正しく読んだ場合、<txid_snapshot_xmax()であり、含まれていないトランザクションで行われたすべての変更txid_snapshot_xip()は、そのフェッチ トランザクションで返されるはずです。この情報は、再度フェッチするときにすべての更新行を取得するために必要なすべてです。列をxmin_version置き換えると、選択は次のようになります。modified

SELECT * FROM the_table WHERE
   xmin_version >= last_fetch_txid_snapshot_xmax OR xmin_version IN last_fetch_txid_snapshot_xip;

トリガーは次のようになります。

CREATE FUNCTION update_xmin_version()
    RETURNS trigger
    LANGUAGE ‘plpgsql’
AS $$
    BEGIN
        NEW.xmin_version = txid_current();
        RETURN NEW;
    END;
$$;

CREATE TRIGGER update_timestamp_update
    BEFORE UPDATE ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_xmin_version();

CREATE TRIGGER update_timestamp_update_insert
    BEFORE INSERT ON the_table
    FOR EACH ROW EXECUTE PROCEDURE update_xmin_version();

これは機能しますか?または、何か不足していますか?

4

2 に答える 2

2

からの64ビットの戻り値txid_current()とエポックがどのようにロールオーバーするかについて明確にしていただきありがとうございます。そのエポックカウンターをタイムエポックと混同して申し訳ありません。

あなたのアプローチに欠陥は見られtxid_snapshot_xip()ませんが、スナップショットを取得する反復可能な読み取りトランザクションで複数のクライアントセッションを同時に実行しても問題がないことを実験を通じて確認します。

クライアントコードは、同じ変更の重複読み取り (挿入/更新/削除) の処理と、データベースの内容と処理するクライアントのワーキングセットとの間の定期的な調整を解決する必要があると想定しているため、実際にはこの方法を使用しません。通信障害またはクライアント クラッシュによるドリフト。そのコードが書かれたらnow()、クライアント追跡テーブル、clock_timestamp()トリガー、およびクライアントが変更セットをプルするときの猶予間隔オーバーラップで使用すると、私が遭遇したユースケースで機能します。

それよりも強力なリアルタイム整合性が必要な場合は、分散コミット戦略をお勧めします。

于 2020-06-27T14:02:40.053 に答える