테이블 변수가 하나의 값만 보유한 경우에는 문제가 없습니다. 여러 행을 사용하면 교착 상태에 대한 새로운 가능성이 있습니다. 동일한 회사에 대해 (1, 2) 및 (2, 1)을 포함하는 테이블 변수로 실행되는 두 개의 동시 프로세스 (A & B)가 있다고 가정하십시오.
프로세스 A는 대상을 읽고 행을 찾은 다음 값 '1'을 삽입합니다. 값 '1'에 독점 행 잠금을 보유합니다. 프로세스 B는 대상을 읽고 행을 찾은 다음 값 '2'를 삽입합니다. 값 '2'에 독점 행 잠금을 보유합니다.
이제 프로세스 A는 행 2를 처리해야하고 프로세스 B는 행 1을 처리해야합니다. 두 프로세스 모두 다른 프로세스가 보유한 독점 잠금과 호환되지 않는 잠금이 필요하기 때문에 진행할 수 없습니다.
여러 행이있는 교착 상태를 피하려면 매번 같은 순서로 행을 처리하고 테이블에 액세스해야합니다 . 질문에 표시된 실행 계획의 테이블 변수는 힙이므로 행에 본질적인 순서가 없습니다 (이는 보장되지 않지만 삽입 순서로 읽을 가능성이 높습니다).
일관된 행 처리 순서가 없으면 교착 상태가 발생합니다. 두 번째 고려 사항은 키 고유성 보장이 없다는 것은 올바른 할로윈 보호를 제공하기 위해 테이블 스풀이 필요하다는 것을 의미합니다. 스풀은 열성적인 스풀입니다. 즉, 모든 행이 삽입 연산자에 대해 읽고 재생되기 전에 tempdb 작업 테이블에 기록됩니다 .
TYPE
클러스터링을 포함하도록 테이블 변수를 재정의합니다 PRIMARY KEY
.
DROP TYPE dbo.CoUserData;
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL PRIMARY KEY CLUSTERED,
MyValue integer NOT NULL
);
실행 계획에 클러스터 된 인덱스의 스캔이 표시되고 고유성 보장으로 옵티마이 저가 테이블 스풀을 안전하게 제거 할 수 있습니다.
MERGE
128 개 스레드 에서 명령문을 5000 회 반복 한 테스트 에서 클러스터 된 테이블 변수에 교착 상태가 발생하지 않았습니다. 나는 이것이 관찰에 기초한 것임을 강조해야한다. 클러스터 된 테이블 변수는 기술적 으로 행을 다양한 순서로 생성 할 수 있지만 일관된 순서의 가능성이 크게 향상됩니다. 물론 모든 새로운 누적 업데이트, 서비스 팩 또는 새 버전의 SQL Server에 대해 관찰 된 동작을 다시 테스트해야합니다.
테이블 변수 정의를 변경할 수없는 경우 다른 대안이 있습니다.
MERGE dbo.CompanyUser AS R
USING
(SELECT DISTINCT MyKey, MyValue FROM @DataTable) AS NewData ON
R.CompanyId = @CompanyID
AND R.UserID = @UserID
AND R.MyKey = NewData.MyKey
WHEN NOT MATCHED THEN
INSERT
(CompanyID, UserID, MyKey, MyValue)
VALUES
(@CompanyID, @UserID, NewData.MyKey, NewData.MyValue)
OPTION (ORDER GROUP);
또한 명시적인 정렬을 도입하면서 스풀 (및 행 순서 일관성)을 제거 할 수 있습니다.
이 계획은 또한 동일한 테스트를 사용하여 교착 상태를 만들지 않았습니다. 아래의 복제 스크립트 :
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL /* PRIMARY KEY */,
MyValue integer NOT NULL
);
GO
CREATE TABLE dbo.Company
(
CompanyID integer NOT NULL
CONSTRAINT PK_Company
PRIMARY KEY (CompanyID)
);
GO
CREATE TABLE dbo.CompanyUser
(
CompanyID integer NOT NULL,
UserID integer NOT NULL,
MyKey integer NOT NULL,
MyValue integer NOT NULL
CONSTRAINT PK_CompanyUser
PRIMARY KEY CLUSTERED
(CompanyID, UserID, MyKey),
FOREIGN KEY (CompanyID)
REFERENCES dbo.Company (CompanyID),
);
GO
CREATE NONCLUSTERED INDEX nc1
ON dbo.CompanyUser (CompanyID, UserID);
GO
INSERT dbo.Company (CompanyID) VALUES (1);
GO
DECLARE
@DataTable AS dbo.CoUserData,
@CompanyID integer = 1,
@UserID integer = 1;
INSERT @DataTable
SELECT TOP (10)
V.MyKey,
V.MyValue
FROM
(
VALUES
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9)
) AS V (MyKey, MyValue)
ORDER BY NEWID();
BEGIN TRANSACTION;
-- Test MERGE statement here
ROLLBACK TRANSACTION;