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
운영게시판
최근게시물
Oracle Q&A 38554 게시물 읽기
No. 38554
순번을 사용한 timestamp unique 생성.
작성자
김기환(fieber)
작성일
2011-05-16 18:07
조회수
7,581

기존 레코드에 순번을 주고 그 순번을 사용하여 timestamp로 unique 하게 입력을 하려고 합니다.

ex)

1   -> 190001010001

2   -> 190001010002

3   -> 190001010003

 

현재시간으로 하면 동시 레코드 입력시 시간이 중복되서 들어가는 경우가 있어서요.

 

혹시 방법을 알고 계시면 조언 부탁드립니다.

 

 

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

채번하는 부분을 해당 레코드에 Lock을 걸고 MAX+1하도록 처리하셔야 합니다.

물론 이렇게 하면 성능이 저하됩니다.

(1)

SELECT seq  INTO max_seq  FROM test  WHERE seq = (SELECT MAX (seq) FROM test)FOR UPDATE; -- 해당 레코드에 lock을 걸어서 직렬화한다. (다른 세션에서 동일한 SELECT...FOR UPDATE가 들어오면 Lock을 대기해야 합니다.)


(2)
INSERT INTO test
     VALUES (max_seq + 1);  --max_seq는 위의 SELECT에서 얻은 최종 순번

(3)

COMMIT;  -- 트랜잭션이 완결되고 위의 Lock이 모두 해제 된다.

 

이 방법보다는 시퀀스를 사용하는게 성능상 훨씬 좋습니다.

김주현님이 2011-05-17 10:56에 작성한 댓글입니다. Edit

저 하나 궁금한 것이 있는데요...

이 경우 자동 증가하려는 컬럼이 기본키가 아닌가 봅니다?

만약 기본키라면 무결성 에러가 날거니까요...

 

 

김주현님께서 답변하신 글도 아마 키가 아니라고 간주하신 거 같아요? 맞나요?

 

그런데... 키가 아닌데 유일키 순번을 주는 경우가 있나요?

만약 기본키는 아니라도 유일한 순번을 준다는 것 자체가 최소한 후보키라는 것이고 그렇다는 것은 유니크가 걸려야 한다는 뜻이라 생각하는데요....

 

제가 김주현님의 답변이 키가 아닌 경우라고 가정하는 이유는

만약 키라면 굳이 lock 걸 필요 없이 중복되면 자연히 에러가 나기 때문이고요...

lock을 걸어도 동시 트랜잭션의 두번째 insert 하는 세션은 어차피 에러가 나기 때문에 트랜잭션이 실패하므로 lock을 거는 것이 의미가 없기 때문입니다.

 

즉 유일키이기만 하면 insert ~ select nvl(max (),0) + 1 만 해도 원하는 것을 성취하고 두번째는 자동 실패하고 두번째를 실패하지 않게 하려면 시퀀스를 쓰든가 no wait를 사용하여 loop를 태워야 합니다.

 

김흥수(protokhs)님이 2011-05-18 13:58에 작성한 댓글입니다.
이 댓글은 2011-05-18 14:18에 마지막으로 수정되었습니다.

그리고 제가 키가 아니라는 전제하여 테스트를 한 것을 올립니다.

만약 키가 아니면 최초 insert에 운나쁘게 동시 트랜잭션이 되면 중복이 발생할 수 있음을 보여줍니다.

 

create table t_lock
(
    id number
)
/
create or replace procedure pr_1
is
    max_seq         number;
    dt1             date;
begin
    begin
    SELECT nvl(id,0)  INTO max_seq  FROM t_lock  WHERE id = (SELECT MAX (id) FROM t_lock)FOR UPDATE;
    exception when no_data_found then
        max_seq :=0;
    end;
    dt1 := sysdate;
    for v in 1..1000000 loop
        exit when ( sysdate - dt1 > 1/24/60/60 * 30) ;
    end loop;
    insert into t_lock(id) values (nvl(max_seq,0) + 1);
    commit;
end;
/
create or replace procedure pr_2
is
    max_seq         number;
begin
    begin
    SELECT nvl(id,0)  INTO max_seq  FROM t_lock  WHERE id = (SELECT MAX (id) FROM t_lock)FOR UPDATE;
    exception when no_data_found then
        max_seq :=0;
    end;
    insert into t_lock(id) values (nvl(max_seq,0) + 1);
    commit;
end;
/
 

 

이렇게 만들고

두개의 SQLPLUS 세션을 열어서

하나에서는

exec pr_1

다른 하나에서는

exec pr_2

을 실행시키면

중복 데이타가 발생합니다

원리는 이렇습니다.

select for update는 행을 lock 겁니다.

즉 아무것도 없을 때는 lock 걸 행도 없습니다.

그러므로

pr_1이 lock 거는 행은 없습니다.

그런 상태에서 loop를 타며 대기할 때

pr_2 가 들어오면

pr_2 도 lock 걸 행이 없습니다.

결국 pr_2가 먼저 수행되어 한행이 생깁니다

그 다음 대기중이던 pr_1이 수행되면 행이 2개 생깁니다.

 

이런 케이스는 제가 일부러 만들기는 했지만 불가능한 시나리오는 아닙니다

김흥수(protokhs)님이 2011-05-18 14:14에 작성한 댓글입니다.

자꾸 써서 죄송합니다.

제 친구가 왜 lock을 걸어도 에러가 나냐고 물어보아서 혹시 싶어서 설명을 드리려고 들어 왔습니다.

select for update를 쓰든 아니든 모든 select는 내부적인 cursor가 열리는 시점의 스냅샷에서 일관된 읽기를 합니다.

즉 거의 동시 트랜잭션이 발생하여 먼저 테이블에 lock을 건 세션이 아직 insert 후에 commit을 하지 않은 상태일 때 두번째 세션이 select를 하면 lock wait 상태가 되기는 하지만 읽게되는 값은 insert 하기 전 값을 읽습니다.

즉 둘다 같은 값을 읽게 되는 것입니다.

이 것을 테스트하는 방법은 간단합니다

먼저 위의 테이블을 만드는데...

에러를 내기 위해 유니크로 만듭니다.

create table t_lock
(
    id number primary key
)
/
그리고 프로시져를 만듭니다,

create or replace procedure pr_i
is
    max_seq         number;
begin
    begin
    SELECT nvl(id,0)  INTO max_seq  FROM t_lock  WHERE id = (SELECT MAX (id) FROM t_lock)FOR UPDATE;
    exception when no_data_found then
        max_seq :=0;
    end;
    insert into t_lock(id) values (nvl(max_seq,0) + 1);
    commit;
end;
/
 

그리고 한 행 insert

insert into t_lock (id) values(1);

commit;

 

그 다음 세션을 세개 준비합니다.

먼저 제1 세션에서

다음과 같이 update를 하고 commit;을 하지 않습니다.(그래야 lock이 걸려서 동시 상황을 연출합니다.)

update t_lock

set id = id + 1;

 

그런 다음 나머지 두개의 세선에서

각각

exec pr_i

 

exec pr_i

를 실행합니다

그러면

두 세션은 대기 상태가 됩니다.

그 상태에서 첫번째 update를 친 세션을

commit; 합니다.

 

그러면

나머지 두개의 세션중 하나는 insert 가 성공하고 두번째는 실패하게 됩니다.

김흥수(protokhs)님이 2011-05-18 14:33에 작성한 댓글입니다.
[Top]
No.
제목
작성자
작성일
조회
38558에러가 발생한 라인을 알고싶습니다. [3]
김흥수
2011-05-18
6842
38556사용가능한 오라클 라이센스및 종류에 대한문의 [2]
매수신호
2011-05-17
3311
38555ORACLE_HOME 을 조회할 수 있는 뷰가 있나요? [1]
이름
2011-05-17
3227
38554순번을 사용한 timestamp unique 생성. [4]
김기환
2011-05-16
7581
38552올백업 exp가 안되요.... [2]
한상원
2011-05-16
3268
38551쿼리 질문이요 ㅠㅠ [3]
궁금타
2011-05-13
3753
38550시간 정보가 들어 있는 두 칼럼의 몇 분 차이를 알고 싶습니다. [2]
초보
2011-05-12
3988
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2024 DSN, All rights reserved.
작업시간: 0.016초, 이곳 서비스는
	PostgreSQL v16.2로 자료를 관리합니다