해당 자료가 이미 있으면 새 값으로 업데이트하고,
없으면, 새롭게 자료를 기록하는 기능을 디비쟁이들 말로 UPSERT 라고 합니다.
replace 라고 하기도 하고, merge라고 하는 기능인데,
이게 PostgreSQL 쪽에서는 그리 쉽지가 않습니다.
a 테이블의 id=5 인 자료가 있다면,
a.ctime = current_timestamp로 바꾸고,
없다면,
그렇게 insert 하라라는 구문을 만들면,
postgres=# create table a (id int not null primary key, ctime timestamp);
CREATE TABLE
postgres=# with upsert as (
postgres(# update a set ctime = current_timestamp where id = 5 returning id)
postgres-# insert into a select 5, current_timestamp
postgres-# where not exists (select id from upsert where id = 5);
INSERT 0 1
이렇게 9.1 버전의 with 절 기능을 이용하면 될 것이다고 생각합니다.
이 구문을 짧게 생각하면 의도된 대로 움직이는 것 같아 보입니다.
하지만, PostgreSQL에서는 윗 구문에서 UPDATE만 직렬성을 보장하지, 그 다음 구문에 대해서는
직렬성을 보장 하지 않습니다.
즉, 세션1에서 update 처리를 하는데, 해당 자료가 없어, insert 처리를 하려고 하고,
세션2에서도 똑 같은 작업을 하려고 한다면, 둘 중 한 세션은 키 중복 오류를 낼 가능성이 높습니다.
세션1이 먼저 실행되고, 세션2가 나중에 실행되어 a.ctime 값이 세션2의 시간으로 update되어야 함에도 불구하고,
그냥 세션1 실행시간이 기록되고 마는 경우가 발생합니다.
혹 이 문제를 고민해 보신 분이 계시면 깔끔한 방법을 알려주세요.
제가 찾은 방법은
begin;
lock a in exclusive mode;
with .....
end;
이렇게 테이블 전체를 잠그는 방법 뿐이더군요. |