원자 트랜잭션에서 고유 한 위반 방지


15

PostgreSQL에서 원자 트랜잭션을 만들 수 있습니까?

이 행에 테이블 범주가 있다고 가정하십시오.

id|name
--|---------
1 |'tablets'
2 |'phones'

그리고 열 이름에는 고유 한 제약 조건이 있습니다.

내가 시도하면 :

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

나는 얻는다 :

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

답변:


24

@Craig가 제공 한 것 외에도 일부를 수정합니다.

효과적인 포스트 그레스 9.4 , UNIQUE, PRIMARY KEYEXCLUDE제약 즉시 체크 각 행 후 정의 할 때 NOT DEFERRABLE. 이것은 각 문장마다 확인 되는 다른 종류의 NOT DEFERRABLE제약 (현재는 REFERENCES(외부 키)) 과 다릅니다 . 우리는 SO에 관한이 관련 질문에 따라이 모든 것을 해결했습니다.

그것은 것입니다 하지 A에 대한 충분한 UNIQUE(또는 PRIMARY KEYEXCLUDE) 제약 조건으로 DEFERRABLE하여 제시된 코드를 만들기 위해 여러 구문의 일을.

그리고이 목적으로 사용할 수 없습니다ALTER TABLE ... ALTER CONSTRAINT . 문서 당 :

ALTER CONSTRAINT

이 양식은 이전에 작성된 구속 조건의 속성을 변경합니다. 현재 외래 키 제약 조건 만 변경할 수 있습니다 .

대담한 강조 광산. 대신 사용하십시오 :

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

제약 조건을 단일 문 에 삭제하고 다시 추가하면 문제가되는 행을 몰래 숨길 시간 창이 없습니다. 큰 테이블의 경우 기본 고유 인덱스를 삭제하고 다시 작성하는 데 비용이 많이 들기 때문에 어떻게 든 기본 인덱스를 보존하려고합니다. 아아, 그것은 표준 도구로는 가능하지 않은 것 같습니다 (해결책이 있으면 알려주십시오!).

A에 대한 하나의 문 제약, 연기하기에 충분하다 :

UPDATE category c
SET    name = c_old.name
FROM   category c_old
WHERE  c.id     IN (1,2)
AND    c_old.id IN (1,2)
AND    c.id <> c_old.id;

CTE가 포함 된 쿼리도 단일 명령문입니다.

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

그러나 여러 문장 이있는 코드의 경우 실제로 (또는) 제약 조건을 실제로 연기하거나이를 INITIALLY DEFERRED위의 값보다 일반적으로 더 비싼 것으로 정의해야 합니다. 그러나 모든 것을 하나의 문장으로 묶는 것은 쉽지 않을 수 있습니다.

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

(A)의주의 제한 과 관련 FOREIGN KEY있지만, 제약. 문서 당 :

참조 된 컬럼은 참조 된 테이블에서 지연 불가능한 고유 또는 기본 키 제한 조건의 컬럼이어야 합니다.

따라서 두 가지를 동시에 가질 수는 없습니다.


13

내가 이해하는 것처럼, 여기서 문제는 각 문 다음에 제약 조건을 확인하지만 트랜잭션 끝에서 확인하기를 원하므로 중간 상태를 무시하고 이전 상태와 이후 상태를 비교합니다.

그렇다면 연기 가능한 제약 조건으로 가능 합니다 .

참조 SET CONSTRAINTSDEFERRABLE에 설명 된대로 제약 CREATE TABLE.

지연된 제약 조건에는 비용이 따른다는 점에 유의하십시오. 시스템은 커밋 시간을 확인하기 위해 해당 목록을 유지해야하므로 변경이 많은 트랜잭션에는 적합하지 않습니다. 또한 확인 속도가 느립니다.

그래서 아마 당신이 원한다고 생각합니다 :

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

ALTER TABLE제약 조건을 DEFERRABLE;로 설정 하는 데 제한이있는 것으로 보입니다 . 대신 구속 조건을 DROP다시 설정 해야 할 수도 있습니다 ADD.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.