2012-12-03 2 views
4

사용자 작업 수를 유지하는 테이블이 있습니다. 작업이 완료 될 때마다 값을 늘려야합니다. 사용자는 동시에 여러 개의 세션을 가질 수 있으므로 다중 사용자 문제를 피하려면 프로세스가 절대적이어야합니다.SQL Server : 행이없는 경우 하나의 열의 값이 증가합니다.

  • ActionCode 추가하는 함수에
  • UserID 내가 ActionCode를 전달하려는 int
  • Count

int뿐만 varchar로하고 UserID :

테이블은 3 열이 새로운 행 하나도 존재하지 않는 경우, count를 1로 설정합니다. 행이 존재하면, 카운트가 1만큼 증가합니다. ActionCodeUserID이이 테이블의 기본 고유 색인을 구성합니다.

내가 할 필요가 모든 업데이트가 있었다면, 나는이 같은 간단한 일 (AN UPDATE 쿼리가 이미 원자 때문에) 할 수있는 : 나는 SQL에서 원자 거래에 새로 온 사람

UPDATE (Table) 
SET Count = Count + 1 
WHERE ActionCode = @ActionCode AND UserID = @UserID 

합니다. 이 질문은 아마도 여기 여러 부분에서 답변을 얻었지만 그 중 하나를 찾아내는 데 어려움을 겪고 있습니다. 이러한 작업이 자주 발생할 수 있기 때문에 복잡한 작업없이 빠르게 처리 할 수 ​​있어야합니다.

편집 : 죄송합니다. 속은 MySQL how to do an if exist increment in a single query 일 수 있습니다. 나는 수색을 많이했지만 수색은 tsql이었습니다. 일단 내가 대신 sql으로 바꿨다면 그 결과가 최고였습니다. 그것은 원자 (atomic)인지는 분명하지 않지만 그것이 확실 할 것이라고 확신합니다. 다른 사람들이이 질문과 답변에 의해 추가 된 새로운 가치가 있다고 생각하지 않는 한, 아마도 이것을 속임수로 지우겠다고 투표 할 것입니다.

+1

사용중인 RDBMS를 명확히 설명해 주시겠습니까? –

+0

상단에 수정 사항이 추가되었습니다 (MS SQL Server 2008). – eselk

+0

감사합니다. "sql-server"및/또는 "sql-server-2008"태그를 포함하면 일반적으로 도움이됩니다. –

답변

8

단일 원자 사항이 필요한 경우 값을 선택 MERGE

MERGE YourTable AS target 
USING (SELECT @ActionCode, @UserID) AS source (ActionCode, UserID) 
ON (target.ActionCode = source.ActionCode AND target.UserID = source.UserID) 
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1 
WHEN NOT MATCHED THEN 
    INSERT (ActionCode, UserID, [Count]) 
    VALUES (source.ActionCode, source.UserID, 1) 
OUTPUT INSERTED.* INTO #MyTempTable; 

UPDATE를 사용하여 출력을 사용할 수 있도록, 당신은 SQL 서버에있는 가정. 코드가 업데이트되었습니다.

+0

매력적이지만 그 구문은 대부분의 데이터베이스에서는 작동하지 않습니다. 먼저 db OP가 사용하고있는 것을 더 잘 알아보십시오 – Bohemian

+0

예제를 추가해 주셔서 감사합니다. 나는 또한 내가 최종 값/카운트를 반환하기를 원하기 때문에 링크 된 다른 포스트를 통해 이런 식으로 갈 수도 있고 그 부분도 원자적일 수있다. 적어도 옵션을 갖게되어서 좋으므로이 항목을 공개 (사기를 삭제하거나보고하지 않음)로 남겨 둘 것입니다. – eselk

+0

@eselk 원자 적으로 값을 얻으려면'OUTPUT' 절을 사용하십시오. 업데이트 된 답변을 참조하십시오. –

2

SQL Server 2008에서 MERGE를 사용하는 것이 가장 좋은 방법 일 수 있습니다. 그것을 해결하는 또 다른 간단한 방법이 있습니다.

사용자 ID/동작이 존재하지 않는 경우 Count에 0이있는 새 행의 INSERT를 수행하십시오. 이 문이 이미 존재하기 때문에 (다른 동시 세션에 의해 삽입 된 것처럼) 실패하면 단순히 오류를 무시하십시오. 당신이 오류의 가능성을 제거 할을 수행하는 동안 삽입 블록을하려면

, 일부 잠금 힌트를 추가 할 수 있습니다

INSERT dbo.UserActionCount (UserID, ActionCode, Count) 
SELECT @UserID, @ActionCode, 0 
WHERE NOT EXISTS (
    SELECT * 
    FROM dbo.UserActionCount WITH (ROWLOCK, HOLDLOCK, UPDLOCK) 
    WHERE 
     UserID = @UserID 
     AND ActionCode = @ActionCode 
); 

그런 다음 보통의 경우와 같이 + 1과 UPDATE을한다. 문제 해결됨.

DECLARE @NewCount int, 

UPDATE UAC 
SET 
    Count = Count + 1, 
    @NewCount = Count + 1 
FROM dbo.UserActionCount UAC 
WHERE 
    ActionCode = @ActionCode 
    AND UserID = @UserID; 

주 1 : 병합 괜찮을하지만 뭔가가 동시성 문제가 발생하지 않는 것을 의미하지 않는다 하나 개의 문장 (따라서 원자)에서 수행해서 것을 알아야한다.잠금은 쿼리가 실행되는 동안 수명이 다해서 획득되고 해제됩니다. 다음과 같은 쿼리는 원자 일지라도 중복 ID 삽입 시도를 일으키는 동시성 문제를 겪습니다.

INSERT T 
SELECT (SELECT Max(ID) FROM Table) + 1, GetDate() 
FROM Table T; 

주 2 : 나는 사람들이 읽을 기사가 슈퍼 높은 트랜잭션 볼륨 시스템에 경험은보다 높은 동시성을 제공하는 "시도 - 그것 - 다음 - 핸들 어떤 오류"방법을 발견했다 잠금을 획득하고 해제합니다. 이것은 모든 시스템 설계에서 그러하지는 않겠지 만 적어도 고려할만한 가치가 있습니다. 그 이후로이 기사를 여러 번 (지금은 포함) 검색했고 다시 찾을 수 없었습니다 ... 언젠가 그것을 찾고 다시 읽길 바랍니다.

+0

질문에 대한 것입니다. 원자 트랜잭션을 사용하기 때문에 최소한이 문을'BEGIN TRAN'과'END TRAN'으로 마무리하고 격리 수준이 적절한 지 확인해야합니다. 어떤 경우에는 어쨌든 오류를 릴레이하는 대신 삽입하기 전에 점검해야 할 수도 있습니다. –

+0

@SergeBelov UPDATE가 실패하면 INSERT를 롤백하고 싶습니다. 그렇다면 BEGIN TRAN과 END TRAN을 사용해야합니다. 분리 레벨은 상관없이 적절한 잠금없이 INSERT 또는 UPDATE를 수행 할 수 없으므로 분리 레벨은 실제로 문제가되지 않습니다. 오류가 발생하지 않도록 INSERT 문에서 적절한 잠금을 얻으려면 내 대답을 업데이트 한 것과 가까운 잠금이 트릭을 수행합니다. – ErikE

+0

@SergeBelov 초기 답변에서 차단 기능을 사용하지 않았 음을 기억하십시오. 블로킹이 필요한 경우,'SERIALIZABLE'은'HOLDLOCK'에 해당하지만, 그 자체로는 충분하지 않습니다! 나는 동일한 쿼리 ('SET TRANSACTION ISOLATION LEVEL' 대'WITH (LOCKHINT)')에서 잠금을 지정하는 여러 가지 방법을 결합한 코드를 작성하지 않기를 바란다. 특정 쿼리에 HOLDLOCK이 필요한 경우 힌트를 쿼리에 추가합니다. 나는 격리 수준이 의미가 없다고 말하지는 않았지만 여기서 목적을 달성하기에는 충분하지 않았고 따라서 가장 중요한 문제는 아니라고 말했습니다. – ErikE

1

다른 사람이 저장 프로 시저에서 이것을 사용하고 삽입/업데이트 된 행을 반환하는 구문이 필요합니다. (삽입 된. 놀랍지 만 * 업데이트 된 행을 반환하지만 그럴 수 있습니다.) 여기 내가 결국에 끝난 것이다. 내 기본 키 (ActionKey)에 추가 열이 있다는 것을 잊어 버렸습니다. 아래에 반영되어 있습니다. 더 유용한 "Count"만 반환하려는 경우 "output inserted.Count"를 수행 할 수도 있습니다.

CREATE PROCEDURE dbo.AddUserAction 
(
@Action varchar(30), 
@ActionKey varchar(50) = '', 
@UserID int 
) 
AS 

MERGE UserActions AS target 
USING (SELECT @Action, @ActionKey, @UserID) AS source (Action, ActionKey, UserID) 
ON (target.Action = source.Action AND target.ActionKey = source.ActionKey AND target.UserID = source.UserID) 
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1 
WHEN NOT MATCHED THEN 
    INSERT (Action, ActionKey, UserID, [Count]) 
    VALUES (source.Action, source.ActionKey, source.UserID, 1) 
output inserted.*; 
관련 문제