Co dokładnie oznacza „No Predicate Join” w SQL Server?

23

MSDN „ Brakująca klasa zdarzenia predykatu łączenia ” mówi „ wskazuje, że jest wykonywane zapytanie, które nie ma predykatu łączenia ”.

Ale niestety nie wydaje się to takie proste.

Na przykład bardzo prosta sytuacja:

create table #temp1(i int);
create table #temp2(i int);
Select * from #temp1, #temp2 option (recompile);

W tabelach nie ma danych, nie ma też ostrzeżenia, choć oczywiście nie ma predykatu łączenia.

Jeśli przyjrzę się dokumentacji SQL Server 2005 (to samo łącze, tylko inna wersja serwera), pojawia się dodatkowe zdanie: „ To zdarzenie jest tworzone tylko wtedy, gdy obie strony sprzężenia zwracają więcej niż jeden wiersz. ” doskonały sens w poprzedniej sytuacji. Brak danych, więc obie strony zwracają 0 wierszy i bez ostrzeżenia. Wstaw wiersze, otrzymaj ostrzeżenie. Ok fajnie.

Ale w następnej mylącej sytuacji wstawiam te same wartości w obu tabelach:

Insert into #temp1 (i) values (1)
Insert into #temp1 (i) values (1)
Insert into #temp2 (i) values (1)
Insert into #temp2 (i) values (1)

I dostaję:

-- no warning:
Select * from #temp1 t1 
    inner join #temp2 t2 on t1.i = t2.i 
option (recompile)
-- has warning:
Select * from #temp1 t1 
    inner join (select 1 i union all select 1) t2 on t1.i = t2.i 
option (recompile)

Dlaczego tak jest?

Uwaga : niektóre skrypty, których użyłem do wykrycia tych złych zapytań na moim serwerze.

  1. oczywiście plan wykonania procedur
  2. użył domyślnego śledzenia serwera do znalezienia ostrzeżeń

    Declare @trace nvarchar(500);
    Select @trace = cast(value as nvarchar(500))
    From sys.fn_trace_getinfo(Null)
    Where traceid = 1 and property = 2;
    
    Select t.StartTime, te.name, *
    From sys.fn_trace_gettable(@trace, 1) t
        Inner join sys.trace_events te on t.EventClass = te.trace_event_id
        where EventClass = 80
    order by t.StartTime desc
  3. pamięć podręczna planu wykonania, aby znaleźć te plany z ostrzeżeniami (jak ten)

    WITH XMLNAMESPACES (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
    SELECT
        Cast('<?SQL ' + st.text + ' ?>' as xml) sql_text,
        pl.query_plan,
        ps.execution_count,
        ps.last_execution_time,
        ps.last_elapsed_time,
        ps.last_logical_reads,
        ps.last_logical_writes
    FROM sys.dm_exec_query_stats ps with (NOLOCK)
        Cross Apply sys.dm_exec_sql_text(ps.sql_handle) st
        Cross Apply sys.dm_exec_query_plan(ps.plan_handle) pl
    WHERE pl.query_plan.value('(//Warnings/@NoJoinPredicate)[1]', 'bit') = 1
    Order By last_execution_time desc
    OPTION (RECOMPILE);
Jānis
źródło

Odpowiedzi:

17

Twoje pytanie jest podobne do tego . Czasami SQL Server może usunąć predykat złączenia z pierwotnego zapytania.

W przypadku, gdy zobaczysz ostrzeżenie o predykacie łączenia, SQL Server wykrywa w czasie kompilacji, że tabela stałych ma tylko jedną odrębną wartość i ta wartość 1przepisuje zapytanie jako:

SELECT *
FROM   (SELECT *
        FROM   #temp1 t1
        WHERE  t1.i = 1) t1
       CROSS JOIN (SELECT 1 i
                   UNION ALL
                   SELECT 1) t2 

Istnieje predykat na skanie tabeli w #tempnastępujący sposób[tempdb].[dbo].[#temp1].[i] =(1)

Predykatu łączenia on t1.i = t2.inie można usunąć w ten sposób w czasie kompilacji, gdy używane są dwie tabele lub jeśli tabela stałych zawiera więcej niż jedną odrębną wartość.


Więcej informacji na ten temat można znaleźć w Paul Biały „s optymalizator kwerendy Deep Dive serii.

Martin Smith
źródło