SQL Server 병행 처리 존재 업데이트 솔 루 션 검토

머리말
이 절 에서 우 리 는 병발 에서 가장 흔히 볼 수 있 는 상황 은 바로 업데이트 이다.병발 에서 행 기록 이 존재 하지 않 으 면 삽입 된다.이때 처리 하지 않 으 면 중복 키 를 삽입 하 는 상황 이 쉽게 나타난다.본 고 는 병발 에 존재 하면 행 기록 을 갱신 하 는 7 가지 방안 을 소개 하고 가장 적합 한 해결 방안 을 종합 적 으로 분석 하고 자 한다.
존재 검토 7 가지 방안 업데이트
우선 테스트 표를 만 들 겠 습 니 다.

IF OBJECT_ID('Test') IS NOT NULL
 DROP TABLE Test

CREATE TABLE Test
(
 Id int,
 Name nchar(100),
 [Counter] int,primary key (Id),
 unique (Name)
);
GO
솔 루 션 1(트 랜 잭 션 시작)
저 희 는 저장 과정 을 통일 적 으로 만 들 고 SQLQuery Stress 를 통 해 동시 다발 상황 을 테스트 합 니 다.첫 번 째 상황 을 살 펴 보 겠 습 니 다.

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 BEGIN TRANSACTION
 IF EXISTS ( SELECT 1
    FROM Test
    WHERE Id = @Id )
  UPDATE Test
  SET  [Counter] = [Counter] + 1
  WHERE Id = @Id;
 ELSE
  INSERT Test
    ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO



100 개의 스 레 드 와 200 개의 스 레 드 를 동시에 열 면 중복 키 를 삽입 할 확률 이 비교적 적 습 니 다.
솔 루 션 2(격 리 단 계 를 최소 격 리 단계 로 낮 춘 UNCOMMITED)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
 BEGIN TRANSACTION
 IF EXISTS ( SELECT 1
    FROM Test
    WHERE Id = @Id )
  UPDATE Test
  SET  [Counter] = [Counter] + 1
  WHERE Id = @Id;
 ELSE
  INSERT Test
    ( Id, Name, [Counter] )
  VALUES ( @Id, @name, 1 );
 COMMIT
GO

이 때 문 제 는 해결 방안 과 다 름 이 없습니다.(단 계 를 최소 격 리 단계 로 낮 추 면 줄 기록 이 비어 있 으 면 이전 사무 가 제출 되 지 않 으 면 현재 사무 도 이 줄 기록 이 비어 있 는 것 을 읽 을 수 있 습 니 다.현재 사무 가 삽입 되 어 제출 되면 이전 사무 가 다시 제출 할 때 중복 키 삽입 문제 가 발생 합 니 다)

솔 루 션 3(격 리 단 계 를 최상 위 SERIALIZABLE 로 업그레이드)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
 BEGIN TRANSACTION
 IF EXISTS ( SELECT 1
    FROM dbo.Test
    WHERE Id = @Id )
  UPDATE dbo.Test
  SET  [Counter] = [Counter] + 1
  WHERE Id = @Id;
 ELSE
  INSERT dbo.Test
    ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO

이런 상황 에 서 는 더욱 나 빠 져 서 바로 자물쇠 가 잠 길 수 있다.

이 때 격 리 단 계 를 최고 격 리 단계 로 올 리 면 중복 키 삽입 문 제 를 해결 할 수 있 지만 업데이트 에 대해 서 는 열 쇠 를 가 져 와 제출 하지 않 습 니 다.이때 다른 프로 세 스 가 조회 하여 공유 자 물 쇠 를 가 져 오 는 경우 프로 세 스 간 에 서로 막 혀 서 잠 금 을 가 져 올 수 있 습 니 다.따라서 최고 격 리 단 계 는 병발 문 제 를 해결 할 수 있 지만 잠 금 문 제 를 가 져 올 수 있다 는 것 을 알 게 되 었 습 니 다.
솔 루 션 4(격 리 단계 향상+좋 은 자물쇠)
이때 우 리 는 최고 격 리 단 계 를 추가 하 는 토대 에서 업데이트 자 물 쇠 를 추가 합 니 다.다음 과 같 습 니 다.

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
 BEGIN TRANSACTION
 IF EXISTS ( SELECT 1
    FROM dbo.Test WITH(UPDLOCK)
    WHERE Id = @Id )
  UPDATE dbo.Test
  SET  [Counter] = [Counter] + 1
  WHERE Id = @Id;
 ELSE
  INSERT dbo.Test
    ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO


여러 번 실행 해도 이상 이 발견 되 지 않 았 습 니 다.데 이 터 를 조회 할 때 공유 자물쇠 가 아 닌 업데이트 자 물 쇠 를 사용 하면 데 이 터 를 읽 을 수 있 지만 다른 사 무 를 막 지 않 습 니 다.둘째,지난번 에 데 이 터 를 읽 은 후에 데이터 가 변경 되 지 않 았 는 지 확인 하면 잠 금 문 제 를 해결 할 수 있 습 니 다.이런 방안 은 가능 할 것 같 지만,높 은 병발 이 라면 가능 할 지 모르겠다.
솔 루 션 5(격 리 단 계 를 줄 버 전 으로 업그레이드 하여 SNAPSHOT 제어)

ALTER DATABASE UpsertTestDatabase
SET ALLOW_SNAPSHOT_ISOLATION ON
 
ALTER DATABASE UpsertTestDatabase
SET READ_COMMITTED_SNAPSHOT ON
GO 

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 BEGIN TRANSACTION
 IF EXISTS ( SELECT 1
    FROM dbo.Test
    WHERE Id = @Id )
  UPDATE dbo.Test
  SET  [Counter] = [Counter] + 1
  WHERE Id = @Id;
 ELSE
  INSERT dbo.Test
    ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO

상술 한 해결 방안 에 도 중복 키 삽입 문제 가 발생 할 수 있다.
솔 루 션 6(격 리 단계+표 변수 향상)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 DECLARE @updated TABLE ( i INT );
 
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 BEGIN TRANSACTION
 UPDATE Test
 SET  [Counter] = [Counter] + 1
 OUTPUT DELETED.Id
   INTO @updated
 WHERE Id = @Id;
 
 IF NOT EXISTS ( SELECT i
     FROM @updated )
  INSERT INTO Test
    ( Id, Name, counter )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO



여러 차례 의 인증 을 거 친 것 도 제로 오류 로 표 변수 형식 을 통 해 가능 한 것 같다.
솔 루 션 7(격 리 단계 상승+Merge)
Merge 키 를 통 해 존재 즉 업 데 이 트 를 실현 하고 그렇지 않 으 면 삽입 합 니 다.또한 격 리 단 계 를 SERIALIZABLE 로 설정 해 야 합 니 다.그렇지 않 으 면 중복 키 삽입 문제 가 발생 할 수 있 습 니 다.코드 는 다음 과 같 습 니 다.

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 SET TRAN ISOLATION LEVEL SERIALIZABLE 
 BEGIN TRANSACTION
 MERGE Test AS [target]
 USING
  ( SELECT @Id AS Id
  ) AS source
 ON source.Id = [target].Id
 WHEN MATCHED THEN
  UPDATE SET
    [Counter] = [target].[Counter] + 1
 WHEN NOT MATCHED THEN
  INSERT ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO

여러 번 의 인증 은 100 개의 스 레 드 를 병행 하 든 200 개의 스 레 드 를 병행 하 든 이상 한 정보 가 없다.
총결산
이 절 에서 우 리 는 병발 에서 존재 하 는 업 데 이 트 를 어떻게 처리 하 는 지 상세 하 게 토론 했다.그렇지 않 으 면 문제 의 해결 방안 을 삽입 하 는 것 이다.현재 상기 세 가지 방안 이 가능 하 다.
솔 루 션 1(최고 격 리 단계+자물쇠 업데이트)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 
 BEGIN TRANSACTION;
 
 UPDATE dbo.Test WITH ( UPDLOCK, HOLDLOCK )
 SET  [Counter] = [Counter] + 1
 WHERE Id = @Id;
 
 IF ( @@ROWCOUNT = 0 )
  BEGIN
   INSERT dbo.Test
     ( Id, Name, [Counter] )
   VALUES ( @Id, @Name, 1 );
  END
 
 COMMIT
GO

잠시 이 세 가지 해결 방안 만 생각 할 수 있 습 니 다.개인 적 으로 추천 방안 1 과 방안 3 을 비교 할 수 있 습 니 다.어떤 고견 이 있 으 십 니까?댓 글 을 남 겨 주 십시오.가능 하 다 면 저 는 후속 적 인 보충 을 하 겠 습 니 다.
솔 루 션 2(최고 격 리 단계+표 변수)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 DECLARE @updated TABLE ( i INT );
 
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 BEGIN TRANSACTION
 UPDATE Test
 SET  [Counter] = [Counter] + 1
 OUTPUT DELETED.id
   INTO @updated
 WHERE id = @id;
 
 IF NOT EXISTS ( SELECT i
     FROM @updated )
  INSERT INTO Test
    ( Id, Name, counter )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO


솔 루 션 3(최고 격 리 단계+Merge)

IF OBJECT_ID('TestPro') IS NOT NULL
 DROP PROCEDURE TestPro;
GO
 
CREATE PROCEDURE TestPro ( @Id INT )
AS
 DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))
 SET TRAN ISOLATION LEVEL SERIALIZABLE 
 BEGIN TRANSACTION
 MERGE Test AS [target]
 USING
  ( SELECT @Id AS Id
  ) AS source
 ON source.Id = [target].Id
 WHEN MATCHED THEN
  UPDATE SET
    [Counter] = [target].[Counter] + 1
 WHEN NOT MATCHED THEN
  INSERT ( Id, Name, [Counter] )
  VALUES ( @Id, @Name, 1 );
 COMMIT
GO
잠시 이 세 가지 해결 방안 만 생각 할 수 있 습 니 다.개인 적 으로 추천 방안 1 과 방안 3 을 비교 할 수 있 습 니 다.어떤 고견 이 있 으 십 니까?댓 글 을 남 겨 주 십시오.가능 하 다 면 저 는 후속 적 인 보충 을 하 겠 습 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기