我有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检查失败。
我该怎么做才能解决问题?
每个函数调用都在另一个事务中运行。
答案 0 :(得分:1)
有a)竞争条件,b)如果要确保INSERT
,你应该到LOCK表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;