Utwórz przewodnik planu do pamięci podręcznej (Lazy Spool) wyniku CTE


19

Zwykle tworzę przewodniki po planach, najpierw konstruując zapytanie, które korzysta z właściwego planu, i kopiując je do podobnego zapytania, które tego nie robi. Jest to jednak czasami trudne, szczególnie jeśli zapytanie nie jest dokładnie takie samo. Jaki jest właściwy sposób tworzenia prowadnic planu od zera?

SQLKiwi wspomniał o opracowywaniu planów w SSIS, czy istnieje sposób lub przydatne narzędzie pomagające w opracowaniu dobrego planu dla SQL Server?

Konkretne wystąpienie to CTE: SQLFiddle

with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;

Czy istnieje jakikolwiek sposób, aby wynik wymyślił dokładnie 3 różne guids i nic więcej? Mam nadzieję, że będę w stanie lepiej odpowiadać na pytania w przyszłości, włączając przewodniki po planach z zapytaniami typu CTE, do których wielokrotnie się odwołujemy, aby przezwyciężyć niektóre dziwactwa SQL Server CTE.


Odpowiedzi:


14

Czy istnieje jakikolwiek sposób, aby wynik wymyślił dokładnie 3 różne przewodniki i nic więcej? Mam nadzieję, że będę w stanie lepiej odpowiadać na pytania w przyszłości, włączając przewodniki po planach z zapytaniami typu CTE, do których wielokrotnie się odwołujemy, aby przezwyciężyć niektóre dziwactwa SQL Server CTE.

Nie dzisiaj. Nierekurencyjne wspólne wyrażenia tabelowe (CTE) są traktowane jako wbudowane definicje widoku i są rozszerzane do logicznego drzewa zapytań w każdym miejscu, do którego się odwołują (podobnie jak zwykłe definicje widoku) przed optymalizacją. Drzewo logiczne dla twojego zapytania to:

LogOp_OrderByCOL: Union1007 ASC COL: Union1015 ASC 
    LogOp_Project COL: Union1006 COL: Union1007 COL: Union1014 COL: Union1015
        LogOp_Join
            LogOp_ViewAnchor
                LogOp_UnionAll
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const

            LogOp_ViewAnchor
                LogOp_UnionAll
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const

Zwróć uwagę na dwie kotwice widoku i sześć wywołań funkcji wewnętrznej newidprzed rozpoczęciem optymalizacji. Niemniej jednak wiele osób uważa, że ​​optymalizator powinien być w stanie stwierdzić, że rozwinięte poddrzewa były pierwotnie pojedynczym obiektem odniesienia i odpowiednio uprościć. Pojawiło się również kilka żądań Connect, aby umożliwić jawne zmaterializowanie CTE lub tabeli pochodnej.

Bardziej ogólna implementacja spowodowałaby, że optymalizator rozważyłby zmaterializowanie dowolnych wspólnych wyrażeń w celu poprawy wydajności ( CASEz podzapytaniem to kolejny przykład, w którym mogą wystąpić problemy dzisiaj). Microsoft Research opublikował artykuł (PDF) na ten temat w 2007 roku, choć do tej pory nie został zaimplementowany. Na razie ograniczamy się do jawnej materializacji przy użyciu takich zmiennych, jak tabele tymczasowe.

SQLKiwi wspomniał o opracowywaniu planów w SSIS, czy istnieje sposób lub przydatne narzędzie pomagające w opracowaniu dobrego planu dla SQL Server?

To było po prostu pobożne życzenie i znacznie wykroczyło poza pomysł modyfikacji przewodników po planach. Zasadniczo możliwe jest napisanie narzędzia do bezpośredniej manipulacji programem XML planu, ale bez konkretnej oprzyrządowania optymalizatora użycie tego narzędzia byłoby prawdopodobnie frustrujące dla użytkownika (i programista pomyślałby o tym).

W szczególnym kontekście tego pytania takie narzędzie nadal nie byłoby w stanie zmaterializować zawartości CTE w sposób, który mógłby być wykorzystany przez wielu konsumentów (w tym przypadku zasilenie obu danych wejściowych łączeniem krzyżowym). Optymalizator i silnik wykonawczy obsługują szpule wielu konsumentów, ale tylko do określonych celów - żadnego z nich nie można zastosować w tym konkretnym przykładzie.

Chociaż nie jestem pewien, mam dość silne przeczucie, że RelOps można śledzić (zagnieżdżona pętla, Lazy Spool), nawet jeśli zapytanie nie jest dokładnie takie samo jak plan - na przykład, jeśli dodałeś 4 i 5 do CTE , nadal korzysta z tego samego planu (pozornie przetestowanego na SQL Server 2012 RTM Express).

Istnieje tutaj rozsądna elastyczność. Szeroki kształt planu XML służy do kierowania poszukiwanie ostatecznego planu (choć wiele atrybutów są całkowicie ignorowane np typ partycji na giełdach) i normalne zasady wyszukiwania są znacznie złagodzone, jak również. Na przykład wcześniejsze przycinanie alternatyw opartych na kosztach jest wyłączone, dozwolone jest jawne wprowadzenie połączeń krzyżowych, a operacje skalarne są ignorowane.

Jest zbyt wiele szczegółów, aby je zagłębić, ale nie można wymusić umieszczenia filtrów i skalarów obliczeniowych, a predykaty formularza column = valuesą uogólnione, więc plan zawierający X = 1lub X = @Xmoże być zastosowany do zapytania zawierającego X = 502lub X = @Y. Ta szczególna elastyczność może znacznie pomóc w znalezieniu naturalnego planu wymuszenia.

W konkretnym przykładzie stałą Union All zawsze można zaimplementować jako skanowanie ciągłe; liczba danych wejściowych do Unii Wszystkie nie ma znaczenia.


3

Nie ma możliwości (wersje SQL Server do 2012 r.) Ponownego użycia jednego buforu dla obu wystąpień CTE. Szczegóły można znaleźć w odpowiedzi SQLKiwi. Poniżej znajdują się dwa sposoby dwukrotnego zmaterializowania CTE, co jest nieuniknione ze względu na charakter zapytania. Obie opcje dają wyraźną liczbę przewodników netto równą 6.

Link z komentarza Martina do strony Quassnoi na blogu o planie kierowania CTE był częściowo inspiracją do tego pytania. Opisuje sposób zmaterializowania CTE na potrzeby skorelowanego podzapytania, do którego odwołuje się tylko jeden raz, chociaż korelacja może powodować wielokrotną ocenę. Nie dotyczy to zapytania w pytaniu.

Opcja 1 - przewodnik po planach

Biorąc podpowiedzi z odpowiedzi SQLKiwi, zmniejszyłem przewodnik do absolutnego minimum, które nadal wykona zadanie, np. ConstantScanWęzły zawierają tylko 2 operatory skalarne, które mogą wystarczająco rozwinąć się do dowolnej liczby.

;with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
OPTION(USE PLAN
N'<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="11.0.2100.60" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="1600" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.0444433" StatementText="with cte(guid,other) as (&#xD;&#xA;  select newid(),1 union all&#xD;&#xA;  select newid(),2 union all&#xD;&#xA;  select newid(),3&#xD;&#xA;select a.guid, a.other, b.guid guidb, b.other otherb&#xD;&#xA;from cte a&#xD;&#xA;cross join cte b&#xD;&#xA;order by a.other, b.other;&#xD;&#xA;" StatementType="SELECT" QueryHash="0x43D93EF17C8E55DD" QueryPlanHash="0xF8E3B336792D84" RetrievedFromCache="true">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan NonParallelPlanReason="EstimatedDOPIsOne" CachedPlanSize="96" CompileTime="13" CompileCPU="13" CompileMemory="1152">
            <MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" />
            <OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="157240" EstimatedPagesCached="1420" EstimatedAvailableDegreeOfParallelism="1" />
            <RelOp AvgRowSize="47" EstimateCPU="0.006688" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1600" LogicalOp="Inner Join" NodeId="0" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="0.0444433">
              <OutputList>
                <ColumnReference Column="Union1163" />
              </OutputList>
              <Warnings NoJoinPredicate="true" />
              <NestedLoops Optimized="false">
                <RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="1" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
                  <OutputList>
                    <ColumnReference Column="Union1080" />
                    <ColumnReference Column="Union1081" />
                  </OutputList>
                  <MemoryFractions Input="0" Output="0" />
                  <Sort Distinct="false">
                    <OrderBy>
                      <OrderByColumn Ascending="true">
                        <ColumnReference Column="Union1081" />
                      </OrderByColumn>
                    </OrderBy>
                    <RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="2" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
                      <OutputList>
                        <ColumnReference Column="Union1080" />
                        <ColumnReference Column="Union1081" />
                      </OutputList>
                      <ConstantScan>
                        <Values>
                          <Row>
                            <ScalarOperator ScalarString="newid()">
                              <Intrinsic FunctionName="newid" />
                            </ScalarOperator>
                            <ScalarOperator ScalarString="(1)">
                              <Const ConstValue="(1)" />
                            </ScalarOperator>
                          </Row>
                          <Row>
                            <ScalarOperator ScalarString="newid()">
                              <Intrinsic FunctionName="newid" />
                            </ScalarOperator>
                            <ScalarOperator ScalarString="(2)">
                              <Const ConstValue="(2)" />
                            </ScalarOperator>
                          </Row>
                        </Values>
                      </ConstantScan>
                    </RelOp>
                  </Sort>
                </RelOp>
                <RelOp AvgRowSize="27" EstimateCPU="0.0001074" EstimateIO="0.01" EstimateRebinds="0" EstimateRewinds="39" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Lazy Spool" NodeId="83" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0260217">
                  <OutputList>
                    <ColumnReference Column="Union1162" />
                    <ColumnReference Column="Union1163" />
                  </OutputList>
                  <Spool>
                    <RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="84" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
                      <OutputList>
                        <ColumnReference Column="Union1162" />
                        <ColumnReference Column="Union1163" />
                      </OutputList>
                      <MemoryFractions Input="0" Output="0" />
                      <Sort Distinct="false">
                        <OrderBy>
                          <OrderByColumn Ascending="true">
                            <ColumnReference Column="Union1163" />
                          </OrderByColumn>
                        </OrderBy>
                        <RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="85" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
                          <OutputList>
                            <ColumnReference Column="Union1162" />
                            <ColumnReference Column="Union1163" />
                          </OutputList>
                          <ConstantScan>
                            <Values>
                              <Row>
                                <ScalarOperator ScalarString="newid()">
                                  <Intrinsic FunctionName="newid" />
                                </ScalarOperator>
                                <ScalarOperator ScalarString="(1)">
                                  <Const ConstValue="(1)" />
                                </ScalarOperator>
                              </Row>
                              <Row>
                                <ScalarOperator ScalarString="newid()">
                                  <Intrinsic FunctionName="newid" />
                                </ScalarOperator>
                                <ScalarOperator ScalarString="(2)">
                                  <Const ConstValue="(2)" />
                                </ScalarOperator>
                              </Row>
                            </Values>
                          </ConstantScan>
                        </RelOp>
                      </Sort>
                    </RelOp>
                  </Spool>
                </RelOp>
              </NestedLoops>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>'
);

Opcja 2 - Zdalne skanowanie

Zwiększając koszt zapytania i wprowadzając Zdalne skanowanie, wynik jest materializowany.

with cte(guid,other) as (
  select *
  from OPENQUERY([TESTSQL\V2012], '
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3') x)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;

2

Z całą powagą nie można od początku planów wykonywania XML. Tworzenie ich za pomocą SSIS to science fiction. Tak, to wszystko XML, ale pochodzą one z różnych wszechświatów. Patrząc na bloga Paula na ten temat , mówi on „na wiele sposobów, na jakie pozwala SSIS…”, więc być może źle zrozumiałeś? Nie sądzę, żeby mówił „użyj SSIS do tworzenia planów”, ale raczej „czy nie byłoby wspaniale móc tworzyć plany za pomocą interfejsu przeciągnij i upuść, takiego jak SSIS”. Być może w przypadku bardzo prostego zapytania możesz poradzić sobie z tym, ale jest to odcinek, być może nawet strata czasu. Zajęty, możesz powiedzieć.

Jeśli tworzę plan podpowiedzi PLANU UŻYTKOWANIA lub przewodnika, mam kilka sposobów. Na przykład mogę usunąć rekordy z tabel (np. Na kopii bazy danych), aby wpłynąć na statystyki i zachęcić optymalizator do podjęcia innej decyzji. Użyłem również zmiennych tabeli zamiast całej tabeli w zapytaniu, więc optymalizator uważa, że ​​każda tabela zawiera 1 rekord. Następnie w wygenerowanym planie zastąp wszystkie zmienne tabeli oryginalnymi nazwami tabel i zamień je na plan. Inną opcją byłoby użycie opcji WITH STATS_STREAM UPDATE STATISTICS do fałszowania statystyk, która jest metodą stosowaną przy klonowaniu kopii baz danych zawierających tylko statystyki, np.

UPDATE STATISTICS 
    [dbo].[yourTable]([PK_yourTable]) 
WITH 
    STATS_STREAM = 0x0100etc, 
    ROWCOUNT = 10000, 
    PAGECOUNT = 93

W przeszłości spędziłem trochę czasu na majstrowaniu przy planach wykonania xml i odkryłem, że w końcu SQL po prostu mówi „nie używam tego” i uruchamia zapytanie tak, jak chce.

W twoim konkretnym przykładzie jestem pewien, że wiesz, że możesz użyć set rowcount 3 lub TOP 3 w zapytaniu, aby uzyskać ten wynik, ale myślę, że nie o to ci chodzi. Prawidłowa odpowiedź będzie naprawdę być: użyć tabeli temp. Chciałbym pochwalić, że:) Nieprawidłowa odpowiedź brzmiałaby: „spędzić godziny, a nawet dni, wycinając własny niestandardowy plan wykonania XML, w którym próbujesz nakłonić optymalizator do zrobienia leniwej szpuli dla CTE, która i tak może nawet nie działać, wyglądałaby sprytnie ale byłoby również niemożliwe do utrzymania ”.

Nie staram się być niekonstruktywny, tylko moja opinia - nadzieja, która pomaga.


Poważnie, plany XML są ignorowalne?!, Myślałem, że o to chodziło? Jeśli są nieważni, powinien rzucić.
crokusek

Miałem na myśli nieudane wydarzenie z Przewodnika po planach.
wBob

2

Czy jest jakikolwiek sposób ...

Wreszcie w SQL 2016 CTP 3.0 istnieje sposób, rodzaj:)

Używanie flagi śledzenia i rozszerzony wydarzenia wyszczególnione przez Dmitrija Pilugin tutaj można (nieco arbitralnie) wyłowić trzy unikalne identyfikatory GUID z pośrednich etapów realizacji zapytań.

Uwaga: Ten kod NIE jest przeznaczony do produkcji lub poważnego użycia w odniesieniu do wymuszania planu CTE, a jedynie lekkiego spojrzenia na nową flagę śledzenia i innego sposobu robienia rzeczy:

-- Configure the XEvents session; with ring buffer target so we can collect it
CREATE EVENT SESSION [query_trace_column_values] ON SERVER 
ADD EVENT sqlserver.query_trace_column_values
ADD TARGET package0.ring_buffer( SET max_memory = 2048 )
WITH ( MAX_MEMORY = 4096 KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY = 30 SECONDS, MAX_EVENT_SIZE = 0 KB, MEMORY_PARTITION_MODE = NONE, TRACK_CAUSALITY = OFF , STARTUP_STATE = OFF )
GO

-- Start the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = START;
GO

-- Run the query, including traceflag
DBCC TRACEON(2486);
SET STATISTICS XML ON;
GO

-- Original query
;with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
option ( recompile )
go

SET STATISTICS XML OFF;
DBCC TRACEOFF(2486);
GO

DECLARE @target_data XML

SELECT @target_data = CAST( target_data AS XML )
FROM sys.dm_xe_sessions AS s 
    INNER JOIN sys.dm_xe_session_targets AS t ON t.event_session_address = s.address
WHERE s.name = 'query_trace_column_values'


--SELECT @target_data td

-- Arbitrarily fish out 3 unique guids from intermediate stage of the query as collected by XEvent session
;WITH cte AS
(
SELECT
    n.c.value('(data[@name = "row_id"]/value/text())[1]', 'int') row_id,
    n.c.value('(data[@name = "column_value"]/value/text())[1]', 'char(36)') [guid]
FROM @target_data.nodes('//event[data[@name="column_id"]/value[. = 1]][data[@name="row_number"]/value[. < 4]][data[@name="node_name"]/value[. = "Nested Loops"]]') n(c)
)
SELECT *
FROM cte a
    CROSS JOIN cte b
GO

-- Stop the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = STOP;
GO

-- Drop the session
IF EXISTS ( select * from sys.server_event_sessions where name = 'query_trace_column_values' )
DROP EVENT SESSION [query_trace_column_values] ON SERVER 
GO

Testowane na wersji (CTP3.2) - 13.0.900.73 (x64), tylko dla zabawy.


1

Odkryłem, że traceflag 8649 (wymuszony równoległy plan) wywołał to zachowanie dla lewej kolumny prowadzącej w moich instancjach 2008, R2 i 2012. Nie musiałem używać flagi w SQL 2005, gdzie CTE zachowywał się poprawnie. Próbowałem użyć planu wygenerowanego w SQL 2005 w wyższych instancjach, ale nie sprawdził się.

with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
option ( querytraceon 8649 )

Albo skorzystało z podpowiedzi, przewodnika po planach, w tym podpowiedzi, albo z planu wygenerowanego przez zapytanie z podpowiedzią w PLANU UŻYCIA itd. Wszystko działało. cte newid


Dzięki za próbę jeszcze raz. Zapytanie nie wygląda inaczej z tą flagą śledzenia lub bez tej flagi w roku 2008/2012. Nie jestem pewien, czy to moje wystąpienia SQL Server, czy to, co próbujesz pokazać. Nadal widzę 18 przewodników. Co widzisz?
孔夫子

3 odrębne prowadnice po lewej stronie (kolumna prowadnic), każdy powtarzany trzy razy. 9 unikalnych prowadnic po prawej stronie (kolumna guidb), więc przynajmniej lewy bit zachowuje się tak, jak chcesz lol. Dodałem obraz do innej odpowiedzi, aby, mam nadzieję, trochę wyjaśnić. Małe kroki. Powinienem również zauważyć, że w SQL 2005 otrzymuję 6 unikalnych prowadnic, 3 po lewej, 3 po prawej.
wBob

Zauważyłem też, że usunięcie „wszystkiego” powoduje również uzyskanie 6 unikalnych prowadnic, po 3 z każdej strony.
wBob

Może sprawić, że traceflag nie będzie działał , mając serwer maxdop 1.
wBob
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.