마스터 테이블을 세부 테이블에 조인 할 때 SQL Server 2014에서 더 큰 (자세한) 테이블의 카디널리티 예상을 조인 출력의 카디널리티 예상으로 사용하도록하려면 어떻게해야합니까?
예를 들어 10K 마스터 행을 100K 세부 정보 행에 조인 할 때 SQL Server가 예상 세부 정보 행 수와 같은 100K 행의 조인을 추정하려고합니다. 모든 세부 정보 행에 항상 해당 마스터 행이 있다는 사실을 SQL Server 견적자가 활용하는 데 도움이되도록 쿼리 및 / 또는 테이블 및 / 또는 인덱스를 어떻게 구성해야합니까? (그들 사이의 조인은 카디널리티 추정을 줄이지 않아야 함을 의미합니다.)
자세한 내용은 다음과 같습니다. 데이터베이스에는 마스터 / 세부 테이블 쌍이 있습니다. VisitTarget
각 판매 트랜잭션마다 VisitSale
하나의 행이 있고 각 트랜잭션에서 각 제품마다 하나의 행이 있습니다. 일대 다 관계입니다. 평균 10 개의 VisitSale 행에 대해 하나의 VisitTarget 행입니다.
테이블은 다음과 같습니다. (이 질문의 관련 열로 단순화하고 있습니다)
-- "master" table
CREATE TABLE VisitTarget
(
VisitTargetId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
SaleDate date NOT NULL,
StoreId int NOT NULL
-- other columns omitted for clarity
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitTarget_SaleDate
ON VisitTarget (SaleDate) INCLUDE (StoreId /*, ...more columns */);
-- "detail" table
CREATE TABLE VisitSale
(
VisitSaleId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
VisitTargetId int NOT NULL,
SaleDate date NOT NULL, -- denormalized; copied from VisitTarget
StoreId int NOT NULL, -- denormalized; copied from VisitTarget
ItemId int NOT NULL,
SaleQty int NOT NULL,
SalePrice decimal(9,2) NOT NULL
-- other columns omitted for clarity
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitSale_SaleDate
ON VisitSale (SaleDate)
INCLUDE (VisitTargetId, StoreId, ItemId, SaleQty, TotalSalePrice decimal(9,2) /*, ...more columns */
);
ALTER TABLE VisitSale
WITH CHECK ADD CONSTRAINT FK_VisitSale_VisitTargetId
FOREIGN KEY (VisitTargetId)
REFERENCES VisitTarget (VisitTargetId);
ALTER TABLE VisitSale
CHECK CONSTRAINT FK_VisitSale_VisitTargetId;
성능상의 이유로 가장 일반적인 필터링 열 (예 :)을 SaleDate
마스터 테이블에서 각 세부 사항 테이블 행 으로 복사하여 부분적으로 비정규 화 한 다음 두 테이블에 모두 포함 인덱스를 추가하여 날짜 필터링 쿼리를 더 잘 지원했습니다. 날짜 필터링 된 쿼리를 실행할 때 I / O를 줄이는 데 효과적이지만 마스터 및 세부 정보 테이블을 결합 할 때이 방법으로 카디널리티 추정 문제가 발생한다고 생각합니다.
이 두 테이블을 조인하면 쿼리는 다음과 같습니다.
SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitTarget vt
JOIN VisitSale vs on vt.VisitTargetId = vs.VisitTargetId
WHERE
vs.SaleDate BETWEEN '20170101' and '20171231'
and vt.SaleDate BETWEEN '20170101' and '20171231'
-- more filtering goes here, e.g. by store, by product, etc.
세부 사항 테이블 ( VisitSale
) 의 날짜 필터 는 중복됩니다. 날짜 범위별로 필터링 된 쿼리에 대해 세부 사항 테이블에서 순차 I / O (일명 인덱스 검색 연산자)를 사용할 수 있습니다.
이러한 종류의 쿼리 계획은 다음과 같습니다.
동일한 문제가있는 쿼리의 실제 계획은 여기 에서 찾을 수 있습니다 .
보시다시피, 조인에 대한 카디널리티 추정 (그림의 왼쪽 아래에있는 툴팁)이 4 배가 넘습니다. 실제 2.1M과 0.5M이 비교되었습니다. 이로 인해 특히 쿼리가 더 복잡한 쿼리에 사용되는 하위 쿼리 인 경우 성능 문제 (예 : tempdb로 유출)가 발생합니다.
그러나 조인의 각 분기에 대한 행 수 추정치는 실제 행 수에 가깝습니다. 조인의 상위 절반은 실제 100K 대 164K입니다. 조인의 하반부는 실제 2.1M 행 대 3.7M 예상입니다. 해시 버킷 분포도 좋아 보입니다. 이러한 관찰 결과는 각 테이블에 대한 통계가 정상이며 문제는 조인 카디널리티의 추정이라는 것입니다.
처음에는 각 테이블의 SaleDate 열이 독립적이지만 실제로는 동일하기를 기대하는 문제는 SQL Server라고 생각했습니다. 그래서 판매 날짜에 대한 동등 비교를 조인 조건 또는 WHERE 절에 추가하려고했습니다.
ON vt.VisitTargetId = vs.VisitTargetId and vt.SaleDate = vs.SaleDate
또는
WHERE vt.SaleDate = vs.SaleDate
이 작동하지 않았다. 심지어 카디널리티 추정치가 악화되었습니다! 따라서 SQL Server가 동등한 힌트를 사용하지 않거나 다른 것이 문제의 근본 원인입니다.
이 카디널리티 추정 문제를 해결하고 희망적으로 해결하는 방법에 대한 아이디어가 있습니까? 나의 목표는 마스터 / 디테일 조인의 카디널리티가 조인의 더 큰 ( "디테일 테이블") 입력에 대한 추정과 동일하게 추정되는 것입니다.
중요한 경우 Windows Server에서 SQL Server 2014 Enterprise SP2 CU8 빌드 12.0.5557.0을 실행하고 있습니다. 사용 가능한 추적 플래그가 없습니다. 데이터베이스 호환성 수준은 SQL Server 2014입니다. 서로 다른 여러 SQL Server에서 동일한 동작을 볼 수 있으므로 서버 관련 문제는 아닐 것입니다.
SQL Server 2014 Cardinality Estimator 에는 내가 찾는 동작과 정확히 일치 하는 최적화 기능이 있습니다 .
그러나 새 CE는 큰 테이블과 작은 테이블간에 일대 다 조인 연관이 있다고 가정하는 더 간단한 알고리즘을 사용합니다. 이는 큰 테이블의 각 행이 작은 테이블의 한 행과 정확히 일치한다고 가정합니다. 이 알고리즘은 더 큰 입력의 예상 크기를 결합 카디널리티로 반환합니다.
이상적으로는 "작은"테이블이 여전히 100K 개 이상의 행을 반환하더라도 조인의 카디널리티 예상 값이 큰 테이블의 예상 값과 동일 할 경우이 동작을 얻을 수 있습니다.