한 쌍의 열 (둘 다 int
) 에 조인되는 3 개의 “큰”테이블이 있습니다.
- Table1에는 ~ 2 억 개의 행이 있습니다.
- Table2 ~ 150 만 행
- Table3에는 ~ 600 만 개의 행이 있습니다.
각 테이블에는 클러스터 인덱스가 Key1
, Key2
다음, 하나 더 열을. Key1
카디널리티가 낮고 매우 비뚤어집니다. 항상 WHERE
조항 에서 참조됩니다 . 절 Key2
에서 언급되지 않았습니다 WHERE
. 각 조인은 다 대다입니다.
문제는 카디널리티 추정에 있습니다. 각 조인의 출력 추정치가 더 커지는 대신 작아 집니다 . 결과는 실제 결과가 수백만에 달할 때 최종 수백 명이 줄어 듭니다.
CE가 더 나은 견적을 낼 수있는 방법이 있습니까?
SELECT 1
FROM Table1 t1
JOIN Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
내가 시도한 솔루션 :
- 에 다중 열 통계 작성
Key1
,Key2
- 만들기 톤 에 필터링 된 통계를
Key1
(이 꽤 도움이되지만 나는 데이터베이스에 사용자가 만든 통계의 수천 끝.)
마스킹 된 실행 계획 (나쁜 마스킹에 대해 죄송합니다)
내가보고있는 경우 결과에는 9 백만 개의 행이 있습니다. 새로운 CE는 180 개의 행을 추정합니다. 레거시 CE는 6100 개의 행을 추정합니다.
재현 가능한 예는 다음과 같습니다.
DROP TABLE IF EXISTS #Table1, #Table2, #Table3;
CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3));
CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3));
CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3));
-- Table1
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2),
DataSize (Key1, NumberOfRows)
AS (SELECT 1, 2000 UNION
SELECT 2, 10000 UNION
SELECT 3, 25000 UNION
SELECT 4, 50000 UNION
SELECT 5, 200000)
INSERT INTO #Table1
SELECT Key1
, Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number)
, T1Key3
FROM DataSize
CROSS APPLY (SELECT TOP(NumberOfRows)
Number
, T1Key3 = Number%(Key1*Key1) + 1
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smaller number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table2
SELECT DISTINCT
Key1
, Key2
, T2Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1*10)
T2Key3 = Number
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smallest number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table3
SELECT DISTINCT
Key1
, Key2
, T3Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1)
T3Key3 = Number
FROM Numbers
ORDER BY Number) size;
DROP TABLE IF EXISTS #a;
SELECT col = 1
INTO #a
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
WHERE t1.Key1 = 1;
DROP TABLE IF EXISTS #b;
SELECT col = 1
INTO #b
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN #Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
답변
명확하게 말하면, 옵티마이 저는 이미 다 대다 조인이라는 것을 알고 있습니다. 병합 조인을 강제로 수행하고 예상 계획을 보면 조인이 다 대일 수 있는지 알려주는 조인 연산자의 속성을 볼 수 있습니다. 여기서 해결해야 할 문제는 카디널리티 추정치를 높이는 것입니다. 따라서 제외 된 쿼리 부분에 대해보다 효율적인 쿼리 계획을 얻을 수 있습니다.
나는 결과를 가하고 시도 할 것이라는 점을 우선에서 가입 Object3
및 Object5
임시 테이블에. 게시 한 계획의 경우 51393 행의 단일 열이므로 tempdb의 공간을 거의 차지하지 않습니다. 임시 테이블에서 전체 통계를 수집 할 수 있으며 그 자체만으로도 정확한 최종 카디널리티 추정치를 얻는 데 충분할 수 있습니다. 전체 통계를 수집 Object1
하면 도움이 될 수 있습니다. 카디널리티 추정치는 계획에서 오른쪽에서 왼쪽으로 이동할 때 종종 악화됩니다.
그래도 작동하지 않으면 ENABLE_QUERY_OPTIMIZER_HOTFIXES
데이터베이스 또는 서버 수준에서 쿼리 힌트를 활성화하지 않은 경우 쿼리 힌트를 시도 할 수 있습니다 . Microsoft는 해당 설정 뒤에 SQL Server 2016의 계획 영향을받는 성능 수정 프로그램을 잠급니다. 그중 일부는 카디널리티 추정과 관련이 있으므로 운이 좋을 것이고 수정 중 하나가 쿼리에 도움이 될 것입니다. FORCE_LEGACY_CARDINALITY_ESTIMATION
쿼리 힌트 와 함께 레거시 카디널리티 추정기를 사용해 볼 수도 있습니다 . 특정 데이터 세트는 기존 CE로 더 나은 추정치를 얻을 수 있습니다.
최후의 수단으로 Adam Machanic의 MANY()
기능을 사용하여 원하는 요소에 따라 카디널리티 추정치를 수동으로 늘릴 수 있습니다 . 나는 다른 대답으로 그것에 대해 이야기 하지만 링크가 죽은 것처럼 보입니다. 관심이 있으시면 뭔가 파헤쳐 볼 수 있습니다.
답변
SQL Server 통계에는 통계 개체의 선행 열에 대한 히스토그램 만 포함됩니다. 따라서에 대한 막대 그래프 값을 제공 Key2
하지만로 표시된 행 사이에만 필터링 된 통계를 작성할 수 있습니다 Key1 = 1
. 각 테이블에서 이러한 필터링 된 통계를 작성하면 추정값이 수정되고 테스트 쿼리에 대해 예상되는 동작이 발생합니다. 각각의 새 조인은 최종 카디널리티 추정값에 영향을 미치지 않습니다 (SQL 2016 SP1 및 SQL 2017 모두에서 확인 됨).
-- Note: Add "WITH FULLSCAN" to each if you want a perfect 20,000 row estimate
CREATE STATISTICS st_#Table1 ON #Table1 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table2 ON #Table2 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table3 ON #Table3 (Key2) WHERE Key1 = 1
이러한 필터링 된 통계가 없으면 SQL Server는 조인의 카디널리티를 추정하기 위해보다 휴리스틱 기반 방식을 사용합니다. SQL 서버가 사용하는 휴리스틱 일부의 좋은 높은 수준의 설명이 포함되어 백서 다음 은 SQL Server 2014 리티 견적과 쿼리 계획을 최적화 .
예를 들어 USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
쿼리에 힌트를 추가하면 조인 포함 휴리스틱이 변경되어 Key1
조건 자와 Key2
조인 조건 자간에 독립성이 아니라 일부 상관 관계를 가정 하므로 쿼리에 도움이 될 수 있습니다. 최종 테스트 쿼리의 경우이 힌트는 카디널리티 추정값을에서 (으) 1,175
로 증가 7,551
시키지만 20,000
필터링 된 통계로 생성 된 올바른 행 추정치보다 여전히 약간 부끄럽습니다 .
비슷한 상황에서 우리가 사용한 또 다른 방법은 데이터의 관련 하위 집합을 #temp 테이블로 추출하는 것입니다. 특히 최신 버전의 SQL Server 가 더 이상 #temp 테이블을 디스크에 쓰지 않기 때문에이 방법으로 좋은 결과를 얻었습니다. 다 대다 조인에 대한 설명은 사례의 각 개별 #temp 테이블이 상대적으로 작거나 (최종 결과 집합보다 작음)이 방법을 사용하는 것이 좋습니다.
DROP TABLE IF EXISTS #Table1_extract, #Table2_extract, #Table3_extract, #c
-- Extract only the subset of rows that match the filter predicate
-- (Or better yet, extract only the subset of columns you need!)
SELECT * INTO #Table1_extract FROM #Table1 WHERE Key1 = 1
SELECT * INTO #Table2_extract FROM #Table2 WHERE Key1 = 1
SELECT * INTO #Table3_extract FROM #Table3 WHERE Key1 = 1
-- Now perform the join on those extracts, removing the filter predicate
SELECT col = 1
INTO #c
FROM #Table1_extract t1
JOIN #Table2_extract t2
ON t1.Key2 = t2.Key2
JOIN #Table3_extract t3
ON t1.Key2 = t3.Key2
답변
도달 범위. 시도 이외의 실제 근거는 없습니다.
SELECT 1
FROM Table1 t1
JOIN Table2 t2
ON t1.Key2 = t2.Key2
AND t1.Key1 = 1
AND t2.Key1 = 1
JOIN Table3 t3
ON t2.Key2 = t3.Key2
AND t3.Key1 = 1;