功能中的“插入或更新” - 唯一密钥违规 - 交易问题?

Posted

技术标签:

【中文标题】功能中的“插入或更新” - 唯一密钥违规 - 交易问题?【英文标题】:"Insert or update" in function - unique key violation - transaction issue? 【发布时间】:2013-06-11 13:54:54 【问题描述】:

我有 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】:

有 a) 竞争条件,b) 如果你想确保 INSERT,你应该锁定表

声明 rc int; 开始 在共享行独占模式下锁定表用户; 更新用户 SET counter = counter + 1 WHERE user_id = $1; 获取诊断 rc = ROW_COUNT; 如果 rc = 0 那么 插入用户(ID,计数器)值($1,1) 万一; 结尾;

或更复杂的代码,但锁定更少

声明 rc int; 开始 -- 快速路径 更新用户 SET counter = counter + 1 WHERE user_id = $1; 获取诊断 rc = ROW_COUNT; 如果 rc = 0 那么 在共享行独占模式下锁定表用户; 更新用户 SET counter = counter + 1 WHERE user_id = $1; 获取诊断 rc = ROW_COUNT; 如果 rc = 0 那么 插入用户(ID,计数器)值($1,1) 万一; 万一; 结尾;

【讨论】:

谢谢你的回答,我明天早上测试一下。 :)

以上是关于功能中的“插入或更新” - 唯一密钥违规 - 交易问题?的主要内容,如果未能解决你的问题,请参考以下文章

为银行交易生成唯一密钥

插入批处理,如果在Codeigniter 3 HMVC中有重复的密钥更新

如果唯一键已存在,则插入或更新记录

类似字符的唯一约束违规

Paypal 从 php 中的支付密钥获取交易详情

使用 if exists 使用合并 oracle sql 逻辑插入或更新