postgresql - 函数中的 "Insert or update"- 唯一 key 违规 - 交易问题?

标签 postgresql transactions postgresql-9.2

我有 PostgreSQL 函数,用于计算用户对“项目”的使用情况。 计数器值保存到表中:

users_items

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

最多有。每个用户每个项目 1 个计数器(唯一 key )。

这是我的功能:

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 检查失败。

我可以做什么来解决这个问题?

每个函数调用都在另一个事务中运行。

最佳答案

存在 a) 竞争条件,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;

关于postgresql - 函数中的 "Insert or update"- 唯一 key 违规 - 交易问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17045923/

相关文章:

php - postgresql 事务并发测试脚本

php - 敏捷并行数据库迁移

python - PostgreSQL - Psycopg2 - copy_from - 编码 "UTF8": 0x00 的字节序列无效

performance - 一张大表的 Postgresql 性能?

firebase - Firestore 事务多次触发导致错误数据

Node.js - 使用 Redis 原子更新进行扩展

ruby-on-rails - 无法将数组存储在 postgresql (rails) 的 json 字段中无法将数组转换为 json

postgresql - 使用 sqlalchemy 如何调用返回表的函数

c - 用C程序从PostgreSQL中检索数据

mysql - 使用事务执行脚本