2

ユーザーによる「アイテム」の使用をカウントするために使用されるPostgreSQL関数があります。カウンター値はテーブルに保存されます:

ユーザー_アイテム

user_id - integer (fk)
item_id - integer (fk)
counter - integer

最大あります。アイテムごとにユーザーごとに 1 つのカウンター (一意のキー)。

これが私の機能です:

CREATE OR REPLACE FUNCTION increment_favorite_user_item (item integer, userid integer) RETURNS integer AS 
$BODY$
    DECLARE
        new_count  integer;   -- Usage counter
    BEGIN
        IF NOT EXISTS(SELECT 1 FROM users_items WHERE user_id = userid AND item_id = itemid) 
        THEN
            INSERT INTO users_items ("user_id", "item_id", "counter") VALUES (userid, itemid, 1); -- First usage - create new counter
            new_amount = 1;
        ELSE
            UPDATE users_items SET count = count + 1 WHERE (user_id = userid AND item_id = itemid); -- Increment counter
            SELECT counter INTO new_count FROM users_items WHERE (user_id = userid AND item_id = itemid);
        END IF;

        RETURN new_count;
    END;
$BODY$
LANGUAGE 'plpgsql'
VOLATILE;

これはアプリケーションによって使用され、複数回呼び出すことができます。アイテムが特定のユーザーにとって新しい場合 (users_items テーブルのレコードが存在しない場合)、同じユーザーとアイテムに対して関数を次々に呼び出すまで、すべてが正常に機能します。

2 回目の関数呼び出しで、「キー (user_id, item_id)=(1, 7912) は既に存在します」という固有の違反が発生します。「存在しない場合」チェックが正しく機能しないようです.2番目の関数呼び出しは最初の関数呼び出しによって挿入されたレコードを認識せず、同じ行を挿入しようとするため、uq チェックが失敗します。

問題を解決するにはどうすればよいですか?

すべての関数呼び出しは、別のトランザクションで実行されます。

4

1 に答える 1

2

a)競合状態があります。b)INSERTを確実にする場合は、テーブルをロックする必要があります

DECLARE rc int;
始める
  SHARE ROW EXCLUSIVE MODEでテーブルユーザーをロックします。
  UPDATE ユーザー SET カウンター = カウンター + 1 WHERE user_id = $1;
  GET DIAGNOSTICS rc = ROW_COUNT;
  もしRC = 0 THEN
    INSERT INTO users(id, counter) VALUES($1, 1)
  END IF;
終わり;

またはより複雑なコードですが、ロックは少なくなります

DECLARE rc int;
始める
  -- ファスト パス
  UPDATE ユーザー SET カウンター = カウンター + 1 WHERE user_id = $1;
  GET DIAGNOSTICS rc = ROW_COUNT;
  もしRC = 0 THEN
    SHARE ROW EXCLUSIVE MODEでテーブルユーザーをロックします。
    UPDATE ユーザー SET カウンター = カウンター + 1 WHERE user_id = $1;
    GET DIAGNOSTICS rc = ROW_COUNT;
    もしRC = 0 THEN
      INSERT INTO users(id, counter) VALUES($1, 1)
    END IF;
  END IF;
終わり;
于 2013-06-11T14:38:08.763 に答える