SQL 질문과 답변 게시판
안녕하세요, 작은 모바일 인터넷 회사에서 DBA 비스므래한 일을 하고 있는 북설이라고 합니다^^
문제가 발생한것은 대략 300만 줄 정도 들어간 접속자정보 테이블 입니다.
CREATE TABLE [dbo].[tbl_meminfo](
[USER_NO] [int] NOT NULL,
[IMAGE_VER] [char](3) NOT NULL,
[VOD_INFO] [varchar](10) NOT NULL,
[LAN_INFO] [varchar](10) NOT NULL,
[FLASH_INFO] [varchar](10) NOT NULL,
[SCREEN_WIDTH] [int] NOT NULL,
[SCREEN_HEIGHT] [int] NOT NULL,
[MODEL] [varchar](30) NOT NULL,
[MSG_INFO] [varchar](10) NOT NULL,
[BROWSER_INFO] [varchar](10) NOT NULL,
[BROWSER_NAME] [varchar](20) NULL,
CONSTRAINT [PK_tbl_meminfo] PRIMARY KEY NONCLUSTERED
(
[USER_NO] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
새로 사용자가 들어올때마다 아래와 같이 쿼리를 해서 접속정보를 최신으로 유지 시키는 테이블입니다.
IF EXISTS (SELECT top 1 USER_NO FROM tbl_meminfo WHERE USER_NO = [유저번호])
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
ELSE
INSERT tbl_meminfo ~~;
이 정보를 페이지에서 자주 조회해서 적절한 화면을 보여주는데 씁니다...
SELECT TOP 1 * FROM tbl_meminfo WHERE USER_NO = [유저번호]
문제는 사용자가 많아지면서 다음과 같은 에러가 뜬다는 겁니다...
'PRIMARY KEY 제약 조건 'PK_tbl_meminfo을(를) 위반했습니다. tbl_meminfo 에 중복키를 삽입할 수 없습니다'
원인이 어디 있고, 어떤 방법을 쓰면 해결 할 수 있을지 알수있었으면 좋겠네요... 비슷한 문제를 해결하신 분 있으시면 코멘트 부탁드리겠습니다.
아, 설명이 빠진게 있었네요. 유저번호는 별도의 테이블에서 생성되고 있고, identity 값입니다.
이 테이블의 용도는 유저번호별로, 각 유저번호가 가지고 있는 접속자 환경을 저장하는 테이블입니다.
사용자가 처음 접속할때 (초기페이지로 들어올때) 이 테이블에 상태를 입력하거나 업데이트 하는 거죠.
즉, 한 유저번호에 대해서 이 테이블은 한 컬럼만을 허용해야 합니다.
문제는 if exists 로 컬럼의 존재를 확인해서 없을때만 insert 해야 함에도 불구하고 제대로 컬럼의 존재를 확인하지 못하고 컬럼이 있는데도 insert 를 시도하는 경우가 있다는 겁니다..
단순한 로직상의 문제는 아닌거 같아서 혹시 병렬처리의 문제는 아닌지 고민중입니다...
유저번호가 없는 신규회원의 경우 if exists의 유저번호에 어떤 값이 들어가는지
뭔가 이상하다고 댓글달고 있었는데 역시 접속자 환경을 저장하는 테이블이군요..
동시에 해당 구문이 호출되는 경우로 짐작 됩니다.
프로시저나 쿼리를 실행하는 쪽에서 로그를 남기거나 프로파일러로 추적하셔서 밀리세컨 이하의 짧은 시간에
처음접속하는 유저번호가 호출되는것인지 확인해야할 것 같습니다.
만약 그 경우라면 락을 걸어서 원자성을 확보할 수도 있으나 그렇게 되면 부하가 커지므로
신규가입시에 tbl_meminfo에 insert만 하고
해당 구문에서는 존재 유무를 확인할 필요없이 update만 하는 것이 좋을 것 같습니다.
update 후 @@ERROR 및 @@ROWCOUNT를 체크해서 처리하면 더 완벽할 것 같구요.
동시성 문제인 것 같습니다.
SQL 2008이라면 MERGE문으로 처리하시면되고, 2005라면 아래와 같이 하시면 됩니다.
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
IF @@ROWCOUNT = 0
INSERT tbl_meminfo ~~;
위 상태로 어느 정도 증상 완화가 가능하구요..
완벽히 처리하시고자 한다면 아래처럼하는 방법도 있습니다.
BEGIN TRY
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
IF @@ROWCOUNT = 0
INSERT tbl_meminfo ~~;
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627 AND ERROR_MESSAGE() LIKE N'%PK\_tbl\_meminfo%' ESCAPE(N'\')
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
END CATCH
답변 감사합니다^^ 위에서 제시하신 방식중에
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
IF @@ROWCOUNT = 0
INSERT tbl_meminfo ~~;
를 사용하도록 바꾼 다음에 테스트 하는 중입니다.
저는 아래와 같은 방식으로 begin try 문을 사용할까 고민하고 있었거든요..
IF EXISTS (SELECT top 1 USER_NO FROM tbl_meminfo WHERE USER_NO = [유저번호])
UPDATE tbl_meminfo SET ~~ WHERE USER_NO = [유저번호];
ELSE
BEGIN
BEGIN TRY
INSERT tbl_meminfo ~~;
END TRY
BEGIN CATCH
UPDATE tbl_meminfo ~~
END CATCH
END
이 방식과 begin try 중에서 부하가 적은 쪽으로 작업하려고 합니다.^^ 감사합니다~~


unique id 를 만들기 위해서 테이블을 하나 만들고 identity 번호를 딴 다음에 insert 하세요 시작값은 알아서 잘 설정하신 후 사용하세요