2013-06-11 36 views
1

我有PostgreSQL函數用於計算用戶對「項目」的使用情況。 計數器的值保存到表:功能「插入或更新」 - 唯一密鑰違規 - 交易問題?

users_items

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表不存在)。

對於第二個函數調用,我得到唯一的衝突:「Key(user_id,item_id)=(1,7912)已經存在」。 看起來像「如果不存在」檢查不能正常工作,第二個函數調用不會看到第一個插入的記錄,並嘗試插入相同的行,從而使uq檢查失敗。

我能做些什麼來解決這個問題?

每個函數調用都在另一個事務中運行。

回答

1

有一)競爭條件,B),你應該到鎖表,如果你將確保INSERT

 
DECLARE rc int; 
BEGIN 
    LOCK TABLE users IN SHARE ROW EXCLUSIVE MODE; 
    UPDATE users SET counter = counter + 1 WHERE user_id = $1; 
    GET DIAGNOSTICS rc = ROW_COUNT; 
    IF rc = 0 THEN 
    INSERT INTO users(id, counter) VALUES($1, 1) 
    END IF; 
END; 

或更復雜的代碼,但較少的鎖定

 
DECLARE rc int; 
BEGIN 
    -- fast path 
    UPDATE users SET counter = counter + 1 WHERE user_id = $1; 
    GET DIAGNOSTICS rc = ROW_COUNT; 
    IF rc = 0 THEN 
    LOCK TABLE users IN SHARE ROW EXCLUSIVE MODE; 
    UPDATE users SET counter = counter + 1 WHERE user_id = $1; 
    GET DIAGNOSTICS rc = ROW_COUNT; 
    IF rc = 0 THEN 
     INSERT INTO users(id, counter) VALUES($1, 1) 
    END IF; 
    END IF; 
END; 
+0

thanx您的答案,明天早上我會測試它。 :) – cathulhu