database.sarang.net
UserID
Passwd
Database
DBMS
MySQL
ㆍPostgreSQL
Firebird
Oracle
Informix
Sybase
MS-SQL
DB2
Cache
CUBRID
LDAP
ALTIBASE
Tibero
DB 문서들
스터디
Community
공지사항
자유게시판
구인|구직
DSN 갤러리
도움주신분들
Admin
운영게시판
최근게시물
PostgreSQL Q&A 8994 게시물 읽기
No. 8994
UPSERT 직렬성 문제
작성자
김상기(ioseph)
작성일
2012-05-18 16:45
조회수
12,042

해당 자료가 이미 있으면 새 값으로 업데이트하고, 

없으면, 새롭게 자료를 기록하는 기능을 디비쟁이들 말로 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;

이렇게 테이블 전체를 잠그는 방법 뿐이더군요. 

이 글에 대한 댓글이 총 2건 있습니다.

이경우, 저는 Row Level Locking 을 이용합니다. 그런데 이게 약간의 전략을 요구하는게, 어떤 Row 에 Lock 을 걸 것인가를 정하는 것이 골치 아프기 때문이예요. 상기님의 예에서는

    select id from upsert where id = 5

에 lock 을 걸 수는 없습니다. 왜냐하면 이 레코드가 없을 수도 있기 때문이예요. 따라서 반드시 존재하는 어떤 레코드에 Lock 을 걸어주어야 하는데, 이것을 저는 모든 테이블의 핵심 정보를 관리하는 별도의 테이블 AllTable 을 설계하고, 이 테이블의 해당 Row 즉 레코드에 Lock 을 걸어주게 됩니다. 예를 들어

    create table a (id int not null primary key, ctime timestamp);

라는 명령으로 테이블 a 을 만들었으면, 이 테이블의 핵심 정보를 가지고 있는 레코드를 AllTable 테이블에 INSERT 합니다. 이렇게 INSERT 된 레코드의 고유번호를 100 이라고 가정하겠읍니다. 이후 a 테이블에 UPSERT 를 하기 전에 AllTable 테이블의 해당 레코드에 Lock 을 거는 것입니다. 쿼리로 예를 들면

BEGIN;

SELECT No FROM AllTalbe Where No=100 FOR UPDATE; -- Row Level Locking

-- UPSERT 쿼리 실행

COMMIT;

위와 같이하면 테이블에 Lock 을 걸지 않고도 원자적인 실행을 보장하게 됩니다.

 

초보대왕님이 2012-05-23 22:51에 작성한 댓글입니다.
이 댓글은 2012-05-24 10:18에 마지막으로 수정되었습니다. Edit

데이타 저장을 프로시져로 처리하고

 

프로시져 내에서는

업데이트를 먼저 하지 않고 인서트를 먼저 시행하고

중복키 에러가 발생하면 그것을 예외로 받아서

업데이트를 처리하면 될거 같습니다.

 

일단 에러가 발생하는데도 예외로 받아지는지 모르겠습니다.

테스트는 해보지 않았네요..

 

업데이트를 먼저하면 또 똑같은 상황이 방복되지 않을까 싶네요.

tyro님이 2012-05-24 12:39에 작성한 댓글입니다. Edit
[Top]
No.
제목
작성자
작성일
조회
8998PostgreSQL와 오라클 비교 부탁드립니다. [1]
이선영
2012-05-30
12201
8996Libeay32.dll 오류 [2]
구자은
2012-05-22
10808
8995현재 관리하고 있는 사이즈가 어떻게 되시나요? [1]
박병훈
2012-05-22
8949
8994UPSERT 직렬성 문제 [2]
김상기
2012-05-18
12042
8992함수의 결과값으로 여러행을 반환하고 싶은데요... [5]
iyob
2012-05-17
9619
8991mysql 에서의 변수 사용.. @rnum := @rnum + 1 같은 표현식을 사용할 수 있나요? [3]
김덕원
2012-05-17
9997
8990php의 exploder같은게 postgreSQL엔 없나요? [2]
이기자
2012-05-14
8701
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2024 DSN, All rights reserved.
작업시간: 0.023초, 이곳 서비스는
	PostgreSQL v16.2로 자료를 관리합니다