SQL Server 병행 처리 존재 업데이트 솔 루 션 검토
9459 단어 SQLServer동시 처리존재 하면 업데이트
이 절 에서 우 리 는 병발 에서 가장 흔히 볼 수 있 는 상황 은 바로 업데이트 이다.병발 에서 행 기록 이 존재 하지 않 으 면 삽입 된다.이때 처리 하지 않 으 면 중복 키 를 삽입 하 는 상황 이 쉽게 나타난다.본 고 는 병발 에 존재 하면 행 기록 을 갱신 하 는 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 을 비교 할 수 있 습 니 다.어떤 고견 이 있 으 십 니까?댓 글 을 남 겨 주 십시오.가능 하 다 면 저 는 후속 적 인 보충 을 하 겠 습 니 다.이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Redash를 사용할 때 몰랐던 SQL을 쓰는 법을 배웠습니다.최근 redash에서 sql을 쓸 기회가 많고, 이런 쓰는 방법이 있었는지와 sql에 대해 공부를 다시하고 있기 때문에 배운 것을 여기에 씁니다. Redash란? 월별로 데이터를 표시하고 싶습니다 주별로 데이터를 표...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.