FOREIGN KEY에서 명시적인 단일 KEY 값으로 MERGE JOIN (INDEX SCAN) 극복 Sales.Orders (

7/11 추가 MERGE JOIN 중 인덱스 스캔으로 인해 교착 상태가 발생합니다. 이 경우 FK 상위 테이블에서 전체 인덱스에 대해 S 잠금을 가져 오려는 트랜잭션이 있지만 이전에 다른 트랜잭션은 X 잠금을 인덱스의 키 값에 둡니다.

작은 예제로 시작하겠습니다 (70-461 cource의 TSQL2012 DB 사용).

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

[custid], [empid], [shipperid][Sales].[Customers], [HR].[Employees], [Sales].[Shippers]그에 따라 관련된 매개 변수입니다 . 각 경우에 Parrent 테이블의 참조 열에 클러스터형 인덱스가 있습니다.

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

외래 키를 제외하고 동일한 구조를 가진 INSERT [Sales].[Orders] SELECT ... FROM다른 테이블을 찾으려고 합니다. 테이블을 언급하는 또 다른 중요한 것은 클러스터 된 인덱스입니다.[Sales].[OrdersCache][Sales].[Orders][Sales].[OrdersCache]

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

소량의 데이터를 삽입하려고 할 때 예상대로 LOOP JOIN은 외래 키에서 인덱스 검색을 잘 수행합니다.

많은 양의 데이터를 사용하는 경우 쿼리 최적화 프로그램은 MERGE JOIN을 사용하여 쿼리에서 키를 유지 관리하는 가장 효율적인 방법으로 사용합니다.

그리고 우리의 경우에는 OPTION (LOOP JOIN)을 사용하여 외래 키를 사용하거나 명시적인 JOIN의 경우 INNER LOOP JOIN을 사용하여 실행하는 것과는 아무런 관련이 없습니다.

아래는 내 환경에서 실행하려는 쿼리입니다.

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

계획을 살펴보면 MERGE JOIN으로 3 개의 모든 앞쪽 키가 검증되었음을 알 수 있습니다. 전체 인덱스 잠금과 함께 INDEX SCAN을 사용하기 때문에 적절한 방법이 아닙니다.
외래 키 확인 중 MERGE JOIN

OPTION (LOOP JOIN)을 사용하는 것은 MERGE JOIN보다 거의 15 % 더 비싸기 때문에 적합하지 않습니다 (데이터 볼륨이 증가함에 따라 회귀가 더 커질 것이라고 생각합니다).

SELECT 문에서 shipperid삽입 된 전체 세트의 속성에 대한 단일 값을 볼 수 있습니다 . 제 생각에는 적어도 불변 속성에 대해 삽입 된 세트의 유효성 검사 단계를 더 빠르게 할 수있는 방법이 있어야합니다. 다음과 같은 것 :

  • JOIN 유효성 검사에 대해 정의되지 않은 하위 집합이있는 경우 LOOP JOIN, MERGE JOIN, HASH JOIN 만들기
  • 유효성 검사 열의 명시 적 값이 하나만있는 경우 유효성 검사를 한 번만 수행합니다 (INDEX SEEK).

코드 구조, 추가 DDL 객체 등을 사용하여 위의 상황을 능가하는 일반적인 패턴이 있습니까?

20/07이 추가되었습니다. 해결책. Query Optimizer는 이미 MERGE JOIN을 사용하여 ‘단일 키-외래 키’유효성 검증 최적화를 수행합니다. 또한 Sales.Shippers 테이블 만 만들어 쿼리의 다른 조인에 대해 LOOP JOIN을 동시에 남겨 둡니다. 부모 테이블에 몇 개의 행이 있으므로 Query Optimizer는 정렬 병합 조인 알고리즘을 사용하고 내부 테이블의 각 행을 부모 테이블과 한 번만 비교합니다. 단일 키 유효성 검사 중에 세트에서 단일 값을 효과적으로 처리하는 특정 메커니즘이 있다면 이것이 내 질문에 대한 대답입니다. 그것은 완벽한 결정은 아니지만 SQL Server가 사례를 최적화하는 방식입니다.

성능 영향 조사에 따르면 필자의 경우 MERGE JOIN 및 LOOP JOIN insert 문은 다음과 같은 MERGE JOIN (CPU 시간 리소스)보다 우월한 750 개의 동시 삽입 행과 거의 같습니다. 따라서 OPTION (LOOP JOIN)을 사용하는 것이 비즈니스 프로세스에 적합한 솔루션입니다.



답변

OPTION (LOOP JOIN)을 사용하면 MERGE JOIN보다 거의 15 % 더 비싸므로 적합하지 않습니다.

실행 계획 출력에 표시되는 비용 백분율은 실행 후 계획 (실제) 계획에서도 항상 최적화 모델 추정치입니다. 이러한 비용은 특정 하드웨어의 실제 런타임 성능을 반영하지 않을 수 있습니다. 확실한 유일한 방법은 워크로드로 대안을 테스트하여 가장 중요한 메트릭 (경과 시간, CPU 사용량 등)을 측정하는 것입니다.

내 경험상 옵티마이 저는 루프 조인에서 외래 키 유효성 검사를위한 병합 조인으로 너무 일찍 전환합니다. 가장 큰 작업을 제외하고는 루프 조인이 바람직하다는 것을 알았습니다. 그리고 “큰”이라는 말은 적어도 수천만 개의 행을 의미합니다. 확실히 천이 아니므로 질문에 대한 의견에 표시하십시오.

제 생각에는 적어도 불변 속성에 대해 삽입 된 세트의 유효성 검사 단계를 더 빠르게 할 수있는 방법이 있어야합니다.

이것은 원칙적으로 의미가 있지만 오늘날 외래 키 유효성 검사에서 사용하는 계획 수립 논리에는 그러한 논리가 없습니다. 현재의 논리는 의도적으로 매우 포괄적이며 더 넓은 쿼리에 직교합니다. 특정 최적화는 테스트를 복잡하게하고 엣지 케이스 버그의 가능성을 높입니다.

코드 구조, 추가 DDL 객체 등을 사용하여 위의 상황을 능가하는 일반적인 패턴이 있습니까?

내가 아는 것은 아닙니다. 병합 조인 외래 키 유효성 검사의 교착 상태 위험은 잘 알려져 있으며 가장 널리 사용되는 해결 방법이 OPTION (LOOP JOIN)힌트입니다. 외래 키 유효성 검사 중에 공유 잠금이 수행되는 것을 피할 수있는 방법은 없습니다. 행 잠금 버전 격리 수준에서도 정확성필요하기 때문 입니다.

여러 동시 프로세스가 트랜잭션을 통해 부모 및 자식 테이블에 행을 추가하는 기능을 유지하려는 경우 루프 조인 힌트보다 더 나은 일반적인 대답은 없습니다. 그러나 데이터 수정 (읽기 아님)을 직렬화하려는 경우 sp_getapplock을 사용 하는 것이 안정적이고 간단한 기술입니다.