无法选择UNIQUE VIOLATION的原因

时间:2017-02-09 08:05:15

标签: sql postgresql plpgsql

我有一个表ContentAddressedFiles,其中hashsizeextension列的组合为UNIQUE。我想创建一个存储过程,在调用时,将使用给定值将新记录插入到表中。如果已存在这些值的记录,我想返回该现有记录。这是我的方法:

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的原因(不是id发生冲突,它只是<hash, size, extension>的元组。

1 个答案:

答案 0 :(得分:1)

评论回答了如何避免这个问题的问题;我将在这里解释为什么 PostgreSQL以观察到的方式运行。

原因是函数中的INSERTSELECT语句看到数据库的不同快照(状态),因为事务以默认隔离级别运行READ COMMITTED。在该隔离级别,每个语句都会获得一个新的数据库快照。

对观察到的行为的解释必须是并发事务删除或修改失败的INSERT和后面的SELECT语句之间的行,以便导致{的约束违反的行INSERT运行时,{1}}不再存在。

有两种方法可以解决这个问题:

  • 选择更高的隔离级别:然后两个语句都将看到数据库的相同快照,SELECT将找到阻止INSERT的行,即使它有在此期间被改变了。这没问题,只是意味着整个事务在拍摄快照时逻辑上发生。

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