내가 관심이있는 하나의 열을 선택하는 것보다이 쿼리의 모든 결과 열을 선택하는 것이 더 빠른 이유는 무엇입니까?


13

를 사용하면 select *읽기 수가 훨씬 적을뿐만 아니라를 사용하는 것보다 훨씬 적은 CPU 시간을 사용 하는 쿼리가 select c.Foo있습니다.

이것은 쿼리입니다.

select top 1000 c.ID
from ATable a
    join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
    join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
    and b.IsVoided = 0
    and c.ComplianceStatus in (3, 5)
    and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate

이 작업은 대부분 표 B에서 2,473,658 개의 논리적 읽기로 끝났습니다. 26,562 개의 CPU를 사용했으며 7,965의 지속 시간을 가졌습니다.

이것은 생성 된 쿼리 계획입니다.

단일 열 값 선택 계획 PasteThePlan에서 : https://www.brentozar.com/pastetheplan/?id=BJAp2mQIQ

내가 변경하는 경우 c.ID*쿼리는 논리적 107,049 상당히 균등 세 테이블 사이에 확산, 읽기 완료. CPU 4,266 개를 사용했으며 지속 시간은 1,147입니다.

이것은 생성 된 쿼리 계획입니다.

모든 값을 선택하여 계획 PasteThePlan에서 : https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ

나는 Joe Obbish가 제안한 쿼리 힌트를 다음과 같은 결과와 함께 사용하려고 시도했다.
select c.ID힌트없이 : https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.ID 힌트 : https://www.brentozar.com/pastetheplan/ ? ID = B1W ___ N87
select * 힌트없이 : https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select * 힌트 : https://www.brentozar.com/pastetheplan/?id=rJhhudNIQ

OPTION(LOOP JOIN)힌트와 함께 힌트를 사용하면 힌트가 select c.ID없는 버전과 비교하여 읽기 수를 크게 줄 였지만 힌트 select *없이 쿼리 를 읽는 횟수는 여전히 약 4 배 입니다. 추가 OPTION(RECOMPILE, HASH JOIN)받는 select *쿼리하면 내가 시도 무엇보다 훨씬 더 펼쳤습니다.

테이블과 사용하여 인덱스에 대한 통계를 업데이트 한 후에 WITH FULLSCANselect c.ID쿼리가 실행되고 훨씬 더 빨리 :
select c.ID업데이트를하기 전에 : https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select * 갱신하기 전에 : https://www.brentozar.com/ ? pastetheplan / ID = ryrvodEUX
select c.ID 갱신 후 : https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select * 갱신 후 : https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m

select *select c.ID총 지속 시간 및 총 읽기 ( select *약 절반의 읽기 수) 측면에서 여전히 성능 이 뛰어나지 만 더 많은 CPU를 사용합니다. 전반적으로 업데이트 이전보다 훨씬 더 가깝지만 계획은 여전히 ​​다릅니다.

2016 년 호환성 모드에서 2014 년에 실행되는 2016 년에도 동일한 동작이 나타납니다. 두 계획의 차이를 설명 할 수있는 것은 무엇입니까? "올바른"색인이 작성되지 않았을 수 있습니까? 통계가 약간 오래된 것이 원인 일 수 있습니까?

ON여러 가지 방법으로 술어를 조인 부분으로 이동하려고 시도 했지만 쿼리 계획은 매번 동일합니다.

인덱스 재 구축 후

쿼리와 관련된 세 개의 테이블에서 모든 인덱스를 다시 작성했습니다. c.ID여전히 가장 많은 읽기를 수행하고 있지만 (의 두 배 이상 *) CPU 사용량은 *버전의 약 절반입니다 . 이 c.ID버전은 ATable다음
c.ID과 같이 정렬하여 tempdb에 유출되었습니다 . : https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
* : https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ

나는 또한 병렬 처리없이 강제로 작동하려고 시도했으며 그 결과 나에게 가장 우수한 쿼리를 얻었습니다 : https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX

단일 스레드 버전에서는 1,000 번만 주문을 수행하는 큰 인덱스 검색 후에 연산자의 실행 횟수를 알 수 있지만 Parallelized 버전에서는 2,622에서 4,315 사이의 다양한 연산자 실행이 훨씬 더 많이 수행됩니다.

답변:


4

더 많은 열을 선택하면 요청 된 쿼리 결과를 얻기 위해 SQL Server가 더 열심히 작동해야 할 수도 있습니다. 쿼리 최적화 프로그램이 두 쿼리에 대한 완벽한 쿼리 계획을 제시 할 수 있다면SELECT *모든 테이블에서 모든 열을 선택하는 쿼리보다 오래 실행됩니다. 쿼리 쌍에 대해 반대를 관찰했습니다. 비용을 비교할 때는주의해야하지만 느린 쿼리에는 총 예상 비용이 1090.08 최적화 장치 단위이고 빠른 쿼리에는 총 예상 비용이 6823.11 최적화 장치 단위가 있습니다. 이 경우 옵티마이 저가 총 쿼리 비용을 추정하여 열악한 작업을 수행한다고 말할 수 있습니다. SELECT * 쿼리에 대해 다른 계획을 선택했으며 계획이 더 비쌀 것으로 예상했지만 여기서는 그렇지 않았습니다. 이러한 유형의 불일치는 여러 가지 이유로 발생할 수 있으며 가장 일반적인 원인 중 하나는 카디널리티 추정 문제입니다. 운영자 비용은 카디널리티 추정에 따라 크게 결정됩니다. 계획의 주요 지점에서 카디널리티 추정이 정확하지 않은 경우 계획의 총 비용이 현실을 반영하지 않을 수 있습니다. 이것은 지나치게 단순화 된 것이지만 여기서 무슨 일이 일어나고 있는지 이해하는 데 도움이되기를 바랍니다.

SELECT *쿼리가 단일 열을 선택하는 것보다 비용이 많이 드는 이유에 대해 설명하겠습니다 . SELECT *쿼리는 최적화가 필요한 모든 열을 얻기 위해 추가 작업을 할 필요하거나 더 큰 인덱스에서 읽어 들일 필요가 있습니다 것을 의미 할 수 있습니다 noncovering 인덱스로 일부 커버 인덱스를 설정 할 수 있습니다.SELECT *또한 쿼리 실행 중에 처리해야하는 중간 결과 세트가 더 커질 수 있습니다. 두 쿼리 모두에서 예상 행 크기를 보면이 작업을 볼 수 있습니다. 빠른 쿼리에서 행 크기의 범위는 664 바이트에서 3019 바이트입니다. 느린 쿼리에서 행 크기의 범위는 19-36 바이트입니다. SQL Server는 대량의 데이터를 정렬하거나 해시 테이블로 변환하는 것이 더 비싸다는 것을 알고 있기 때문에 정렬 또는 해시 빌드와 같은 차단 연산자는 행 크기가 더 큰 데이터의 비용이 더 높습니다.

빠른 쿼리를 살펴보면 옵티마이 저는 240 만 개의 인덱스 검색을 수행해야한다고 추정합니다 Database1.Schema1.Object5.Index3. 그것은 대부분의 계획 비용이 나오는 곳입니다. 그러나 실제 계획은 해당 운영자에 대해 1332 개의 인덱스 검색 만 수행했음을 나타냅니다. 루프 조인의 외부 부분에 대한 실제 행과 실제 행을 비교하면 큰 차이가 있습니다. 옵티마이 저는 쿼리 결과에 필요한 처음 1000 개의 행을 찾기 위해 더 많은 인덱스 탐색이 필요하다고 생각합니다. 이것이 쿼리 비용이 상대적으로 높지만 너무 빨리 완료되는 이유입니다. 가장 비싸다고 예상되는 연산자는 예상 작업의 0.1 % 미만이었습니다.

느린 쿼리를 보면 대부분 해시 조인으로 계획을 얻습니다 (루프 조인이 로컬 변수를 처리하기 위해 있다고 생각합니다). 카디널리티 추정은 완벽하지는 않지만, 실제 추정 문제는 유일하게 끝납니다. 나는 대부분의 시간이 수억 행의 테이블을 스캔하는 데 소비된다고 생각합니다.

두 버전의 쿼리 모두에 쿼리 힌트를 추가하여 다른 버전과 연결된 쿼리 계획을 강제하는 것이 도움이 될 수 있습니다. 쿼리 힌트는 옵티마이 저가 일부를 선택한 이유를 파악하는 데 유용한 도구가 될 수 있습니다. 당신이 추가하면 OPTION (RECOMPILE, HASH JOIN)받는 SELECT *쿼리 난 당신이 해시 유사한 쿼리 계획 쿼리에 가입 볼 수 있습니다 기대합니다. 또한 행 크기가 훨씬 커서 해시 조인 계획의 쿼리 비용이 훨씬 높아질 것으로 기대합니다. 따라서 해시 조인 쿼리가 쿼리에 대해 선택되지 않은 이유 일 수 있습니다 SELECT *. OPTION (LOOP JOIN)하나의 열만 선택하는 쿼리에 추가 하면 쿼리 계획과 비슷한 쿼리 계획이 표시됩니다.SELECT *질문. 이 경우 행 크기를 줄이면 전체 쿼리 비용에 큰 영향을 미치지 않습니다. 주요 조회를 건너 뛸 수 있지만 예상 비용의 작은 비율입니다.

요약하면 SELECT *쿼리 를 만족시키는 데 필요한 더 큰 행 크기 는 옵티 마이저를 해시 조인 계획 대신 루프 조인 계획으로 푸시 할 것으로 예상합니다 . 루프 결합 계획은 카디널리티 예상 문제로 인한 것보다 비용이 많이 듭니다. 하나의 열만 선택하여 행 크기를 줄이면 해시 조인 계획의 비용이 크게 줄어들지 만 루프 조인 계획의 비용에는 큰 영향을 미치지 않으므로 해시 조인 계획의 효율성이 떨어집니다. 익명화 된 계획에 대해서는이 이상을 말하기가 어렵습니다.


광범위하고 유익한 답변에 감사드립니다. 제안한 힌트를 추가하려고했습니다. 그것은 했는가 select c.ID훨씬 빠르게 쿼리를하지만 여전히 몇 가지 추가 작업하고있는 select *힌트없이 쿼리, 수행합니다.
L. Miller

2

부실 통계는 확실히 옵티마이 저가 데이터를 찾는 잘못된 방법을 선택하게 할 수 있습니다. 색인에 UPDATE STATISTICS ... WITH FULLSCAN대해 전체 또는 전체 를 시도 했습니까 REBUILD? 시도해보고 도움이되는지 확인하십시오.

최신 정보

OP의 업데이트에 따르면 :

테이블과 사용하여 인덱스에 대한 통계를 업데이트 한 후에 WITH FULLSCANselect c.ID쿼리가 훨씬 빠르게 실행

따라서 이제 취한 유일한 조치가 UPDATE STATISTICS인 경우 색인 과 색인 이 모두 아닌 예상 행 수에 대한 도움말을 보았 듯이 색인 REBUILD(not REORGANIZE) 을 시도하십시오 .UPDATE STATISTICSREORGANIZE


주말에 재건하는 데 관련된 세 개의 테이블에 대한 모든 색인을 얻을 수 있었고 그 결과를 반영하도록 게시물을 업데이트했습니다.
L. Miller

-1
  1. 색인 스크립트를 포함시킬 수 있습니까?
  2. "파라미터 스니핑"과 관련된 가능한 문제를 제거 했습니까? https://www.mssqltips.com/sqlservertip/3257/different-approaches-to-correct-sql-server-parameter-sniffing/
  3. 나는이 기술이 어떤 경우에는 도움이된다는 것을 발견
    했다 .a) 다음 규칙에 따라 각 테이블을 하위 쿼리로 다시 작성하십시오
    .b
    ) SELECT- 조인 열을 먼저 넣습니다 .c) PREDICATES-각 하위 쿼리로 이동합니다.
    d) ORDER BY- 각 하위 쿼리는 JOIN COLUMNS FIRST에서 정렬
    e) 최종 정렬 및 SELECT에 대한 래퍼 쿼리를 추가합니다.

아이디어는 각 하위 선택 내에서 조인 열을 미리 정렬하여 각 선택 목록에서 조인 열을 먼저 정렬하는 것입니다.

여기 내가 의미하는 바가있다 ....

SELECT ... wrapper query
FROM
(
    SELECT ...
    FROM
        (SELECT ClientID, ShipKey, NextAnalysisDate
         FROM ATABLE
         WHERE (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff) -- Predicates
         ORDER BY OrderKey, ClientID, LastAnalyzedDate  ---- Pre-sort the join columns
        ) as a
        JOIN 
        (SELECT OrderKey, ClientID, OrderID, IsVoided
         FROM BTABLE
         WHERE IsVoided = 0             ---- Include all predicates
         ORDER BY OrderKey, OrderID, IsVoided       ---- Pre-sort the join columns
        ) as b ON b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
        JOIN
        (SELECT OrderID, ShipKey, ComplianceStatus, ShipmentStatus, ID
         FROM CTABLE
         WHERE ComplianceStatus in (3, 5)       ---- Include all predicates
             AND ShipmentStatus in (1, 5, 6)        ---- Include all predicates
         ORDER BY OrderID, ShipKey          ---- Pre-sort the join columns
        ) as c ON c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
) as d
ORDER BY d.LastAnalyzedDate

1
1. 색인 DDL 스크립트를 원래 게시물에 추가하려고 시도하는데, "스크럽"하는 데 시간이 걸릴 수 있습니다. 2. 실행 전에 계획 캐시를 지우고 bind 매개 변수를 실제 값으로 대체하여이 가능성을 테스트했습니다. 3.이 작업을 시도했지만 ORDER BYTOP, FORXML 등이없는 하위 쿼리에서 유효하지 않습니다. ORDER BY절 없이 시도했지만 동일한 계획이었습니다.
L. Miller
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.