插入没有排序列的空表时重复键约束错误

时间:2014-12-08 21:41:27

标签: sql database postgresql concurrency triggers

为什么我插入的表格为空时会出现此错误?该表的上一次迭代(自已删除)有一个序列,所以我想知道Postgres是否正在缓存旧表模式?

这是错误:

-- Executing query:
DELETE FROM student
WHERE sno =101;

ERROR:  duplicate key value violates unique constraint "cancel_pkey"
DETAIL:  Key (eno, cdate)=(1, 2014-12-08 21:21:53.710883) already exists.
CONTEXT:  SQL statement "INSERT INTO cancel(eno,excode,sno)
            SELECT eno, excode, sno 
            FROM entry
            WHERE eno = OLD.eno"
PL/pgSQL function cancel_entry() line 3 at SQL statement
SQL statement "DELETE FROM entry
            WHERE sno = OLD.sno"
PL/pgSQL function delete_student() line 8 at SQL statement

实际的插入查询发生在触发器中,如下所示:

CREATE OR REPLACE FUNCTION delete_student()
RETURNS trigger AS $BODY$
    BEGIN
    IF (SELECT EXISTS (SELECT * FROM entry WHERE sno = OLD.sno)) THEN
    INSERT INTO cancel(eno,excode,sno)
        SELECT eno, excode, sno 
        FROM entry
        WHERE sno = OLD.sno;
    DELETE FROM entry
        WHERE sno = OLD.sno;
    END IF;
    RETURN OLD;
    END; $BODY$ 
    LANGUAGE plpgsql;

CREATE TRIGGER DeleteStudent
    BEFORE DELETE ON student
    FOR EACH ROW
    execute procedure delete_student();

cancel表架构如下所示:

CREATE TABLE cancel (
eno     INTEGER     NOT NULL,
excode  CHAR(4)     NOT NULL, --not unique as many instances of cancellations for exam
sno     INTEGER     NOT NULL, --not unique as student may cancel several exams
cdate   TIMESTAMP   NOT NULL    DEFAULT NOW(),
cuser   VARCHAR(128) NOT NULL   DEFAULT CURRENT_USER,
PRIMARY KEY(eno,cdate)
-- sno is not a foreign key, as it must still exist even where student is deleted. 
);

entry表架构是:

CREATE TABLE entry (
eno     INTEGER     NOT NULL    DEFAULT NEXTVAL('eno_sequence'),
excode  CHAR(4)     NOT NULL,
sno     INTEGER     NOT NULL,
egrade  DECIMAL(5,2)    CHECK(egrade BETWEEN 0 AND 100),
PRIMARY KEY(eno),
FOREIGN KEY(excode) REFERENCES exam MATCH FULL ON DELETE RESTRICT,
FOREIGN KEY(sno) REFERENCES student MATCH FULL ON DELETE RESTRICT --handle with stored procedure to retain student reference in cancel table
);

'学生'表模式

CREATE TABLE student (
   sno          INTEGER         NOT NULL,
   sname            VARCHAR(20) NOT NULL,
   semail      VARCHAR(20)  NOT NULL,
    PRIMARY KEY(sno)
);

2 个答案:

答案 0 :(得分:2)

由于您正在从附加entry移动行,并且您首先运行SELECT / INSERT,因此存在潜在的竞争条件:多个并发事务可以同时尝试相同时间。

但是,触发器由表DELETE上的student调用,对受影响的行进行ROW EXCLUSIVE锁定。您DELETEWHERE sno = 101)的谓词位于 唯一 列上。尽管问题中缺少表student的实际表定义,但我可以从表entry中的FK定义中看出,它要求对引用列具有唯一或PK约束。这使得并发事务不会删除student中会在竞争行上调用触发器的行。

首先让我感到困惑的是:您显示了触发器/函数DeleteStudent / delete_student(),但异常是从 cancel_entry() 引发的,这是你问题中的 不是 。走出困境,看起来你在另一个触发器中调用相同的INSERT命令,这将解释异常

无论哪种方式,我们看到的功能更昂贵,并且比竞争条件更容易受到影响。首先使用data-modifying CTE删除(锁定行),然后只插入另一个表(这可能会解决您的问题,缺少信息)。还简化:

CREATE OR REPLACE FUNCTION delete_student()
  RETURNS trigger AS
$func$
BEGIN
   IF (SELECT EXISTS (SELECT * FROM entry WHERE sno = OLD.sno)) THEN -- no need
   WITH del AS (
      DELETE FROM entry  -- delete first, locking rows
      WHERE  sno = OLD.sno
      RETURNING eno, excode, sno
      )
   INSERT INTO cancel(eno, excode, sno) -- only then insert
   SELECT eno, excode, sno 
   FROM   del;
   END IF;
   RETURN OLD;
END
$func$  LANGUAGE plpgsql;

您根本不需要IF构造。如果SELECT(或更新版本中的DELETE)在entry中找不到任何行,则INSERT一无所有。在CTE的情况下,当CTE INSERT没有返回任何行时,永远不会执行最终del。这就像它得到的那样快速和干净。

相关答案:

答案 1 :(得分:0)

目前,我决定在“条目”中使用FOREIGN KEY上的ON DELETE CASCADE约束下面的触发器

CREATE OR REPLACE FUNCTION delete_student()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO cancel(eno, excode, sno)
   SELECT eno, excode, sno 
   FROM   entry
   WHERE  sno = OLD.sno;
   RETURN OLD;
END
$func$  LANGUAGE plpgsql;