2017-02-09 95 views
2

我有一个表ContentAddressedFiles其中列hash,sizeextension的组合是UNIQUE。我想创建一个被调用的存储过程,它会使用给定的值向表中插入一条新记录。如果这些值的记录已经存在,我想只返回该现有记录。这里是我的方法:无法选择UNIQUE VIOLATION的原因

CREATE OR REPLACE FUNCTION INIT_CAF(id_in_case_of_new UUID, _hash VARCHAR(255), _size INTEGER, _extension VARCHAR(255), _mimeType VARCHAR(255)) 
RETURNS "ContentAddressedFiles" 
AS $$ 
DECLARE 
    caf "ContentAddressedFiles"%ROWTYPE; 
BEGIN 

    INSERT INTO "ContentAddressedFiles" (id, hash, size, extension, "mimeType", "createdAt", "updatedAt") 
    VALUES(id_in_case_of_new, _hash, _size, _extension, _mimeType, NOW(), NOW()) RETURNING * INTO caf; 

    RETURN caf; 

EXCEPTION WHEN unique_violation THEN 

    SELECT * FROM "ContentAddressedFiles" INTO caf WHERE "hash" = _hash AND "size" = _size AND "extension" = _extension; 

    IF NOT FOUND THEN 
    RAISE EXCEPTION 'This should never happen.'; 
    END IF; 

    RETURN caf; 

END; 
$$ LANGUAGE plpgsql; 

然而,当我从打电话并发事务的过程中,我始终得到异常:

EXCEPTION: This should never happen. 

这怎么可能?该程序似乎并不能够前SELECT发生故障的INSERT的原因(这不是冲突,它是<hash, size, extension>只是元组中的id

+0

让你看到升起exveption和空值而不是预期的行?..请加你输出的问题 –

+0

是的,正是我得到的异常,当我从不同的事务同时运行的程序。 – DeX3

+0

http://stackoverflow.com/questions/6722344/select-or-insert-a-row-in-one-command –

回答

1

的问题如何避免这个问题的答案为的意见。我“会在这里解释为什么的PostgreSQL中所观察到的方式行事。

的原因是INSERT和功能的SELECT声明查看数据库的不同快照(州),因为事务与默认运行隔离级别为READ COMMITTED。在隔离级别,每条语句都会获得新的数据库快照。

所观察到的行为的解释必须是并发事务删除或修改失败INSERT及以下SELECT语句之间的行,这样导致约束违反了INSERT行不再存在时SELECT运行。

有处理问题的两种方法:

  • 选择一个更高的隔离级别:那么这两个语句会看到数据库的同一快照,并防止INSERT将由找到行尽管它在此期间已经改变,但是该SELECT。这不是问题,它只是意味着整个事务逻辑上发生在拍摄快照时。

  • 将这两个语句作为单个语句与CTE一起运行,就像评论推荐中给出的解决方案一样。然后他们也会看到相同的数据库快照。

+0

是的,你是对的,这是最有可能的情况下(即使竞争条件的窗口很小)。至少对于'VOLATILE'功能;从'STABLE'中,快照是不同的,但是该类别无论如何都不允许使用INSERT。也许这个快照的一个很好的例子是[原始UPSERT示例](https://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING)如何工作。 – pozs

相关问题