私はPython(実際には関連していません)とPostgresql(関連する場合は9.2)を使用して、単純なWebベースのRSSリーダーを実装しています。データベース スキーマは次のとおりです (RSS 形式に基づく)。
CREATE TABLE feed_channel
(
id SERIAL PRIMARY KEY,
name TEXT,
link TEXT NOT NULL,
title TEXT
);
CREATE TABLE feed_content
(
id SERIAL PRIMARY KEY,
channel INTEGER REFERENCES feed_channel(id) ON DELETE CASCADE ON UPDATE CASCADE,
guid TEXT UNIQUE NOT NULL,
title TEXT,
link TEXT,
description TEXT,
pubdate TIMESTAMP
);
新しいチャネルを作成する (および更新されたフィード情報を照会する) ときは、フィードを要求し、そのデータを feed_channel テーブルに挿入し、新しく挿入された ID (または重複を避けるために既存の ID) を選択してから、フィード データを feed_content テーブルに追加します。 . 典型的なシナリオは次のとおりです。
- フィード URL を照会し、フィード ヘッダーと現在のすべてのコンテンツを取得します
- 存在しない場合はフィード ヘッダーを feed_channel に挿入します... 既に存在する場合は、既存の ID を取得します
- フィード アイテムごとに、格納されているチャネル ID への参照を指定して feed_content テーブルに挿入します。
これは標準的な「存在しない場合は挿入するが、関連する ID を返す」問題です。これを解決するために、次のストアド プロシージャを実装しました。
CREATE OR REPLACE FUNCTION channel_insert(
p_link feed_channel.link%TYPE,
p_title feed_channel.title%TYPE
) RETURNS feed_channel.id%TYPE AS $$
DECLARE
v_id feed_channel.id%TYPE;
BEGIN
SELECT id
INTO v_id
FROM feed_channel
WHERE link=p_link AND title=p_title
LIMIT 1;
IF v_id IS NULL THEN
INSERT INTO feed_channel(name,link,title)
VALUES (DEFAULT,p_link,p_title)
RETURNING id INTO v_id;
END IF;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
これは、「select channel_insert(link, title);」と呼ばれます。まだ存在しない場合はアプリケーションから挿入し、挿入されたか見つかったかに関係なく、関連する行の ID を返します (上記のリストのステップ 2)。
これはうまくいきます!
しかし、最近、このプロシージャが同じ引数で同時に 2 回実行されたらどうなるのだろうと考え始めました。次のように仮定します。
- ユーザー 1 が新しいチャネルを追加しようとして、channel_insert を実行します
- 数ミリ秒後、ユーザー 2 は同じチャネルを追加しようとし、channel_insert も実行しようとします。
- ユーザー 1 の既存の行のチェックは完了しますが、挿入が完了する前に、ユーザー 2 のチェックが完了し、既存の行がないことが示されます。
これは PostgreSQL で競合状態になる可能性がありますか? このようなシナリオを回避するために、この問題を解決する最善の方法は何ですか? ストアド プロシージャ全体をアトミックに作成することは可能ですか。つまり、同時に 1 回しか実行できないようにすることはできますか?
私が試した1つのオプションは、フィールドを一意にしてから最初に挿入しようとすることでした.例外の場合は、代わりに既存のものを選択してください. . それが長期的に問題になるかどうかはわかりませんが(おそらくそうではないでしょう)、ちょっと面倒です。おそらくこれが好ましい解決策ですか?
フィードバックをお寄せいただきありがとうございます。このレベルの PostgreSQL マジックは私には理解できないので、フィードバックをいただければ幸いです。