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();
これは機能しますか?または、何か不足していますか?