Sednem pytania nie jest „dlaczego zamówienie ma znaczenie w LINQ?”. LINQ po prostu tłumaczy dosłownie bez zmiany kolejności. Prawdziwe pytanie brzmi „dlaczego te dwa zapytania SQL mają różną wydajność?”.
Udało mi się odtworzyć problem, wstawiając tylko 100 tys. wierszy. W takim przypadku uruchamiana jest słabość optymalizatora:nie rozpoznaje on, że może wykonać wyszukiwanie w Colour
ze względu na złożony stan. W pierwszym zapytaniu optymalizator rozpoznaje wzorzec i tworzy wyszukiwanie indeksu.
Nie ma semantycznego powodu, dla którego tak powinno być. Wyszukiwanie po indeksie jest możliwe nawet przy wyszukiwaniu po NULL
. To jest słabość/błąd w optymalizatorze. Oto dwa plany:
EF próbuje być tutaj pomocny, ponieważ zakłada, że zarówno kolumna, jak i zmienna filtru mogą mieć wartość null. W takim przypadku próbuje podać dopasowanie (co zgodnie z semantyką C# jest właściwe).
Próbowałem to cofnąć, dodając następujący filtr:
Colour IS NOT NULL AND @p__linq__0 IS NOT NULL
AND Size IS NOT NULL AND @p__linq__1 IS NOT NULL
Mając nadzieję, że optymalizator użyje teraz tej wiedzy do uproszczenia złożonego wyrażenia filtru EF. Nie udało się tego zrobić. Gdyby to zadziałało, ten sam filtr mógłby zostać dodany do zapytania EF, zapewniając łatwą poprawkę.
Oto zalecane przeze mnie poprawki w kolejności, w jakiej należy je wypróbować:
- Ustaw kolumny bazy danych nie-null w bazie danych
- Spraw, aby kolumny nie miały wartości NULL w modelu danych EF, mając nadzieję, że uniemożliwi to EF utworzenie złożonego warunku filtru
- Utwórz indeksy:
Colour, Size
i/lubSize, Colour
. Usuwają również ich problem. - Upewnij się, że filtrowanie odbywa się we właściwej kolejności i zostaw komentarz do kodu
- Spróbuj użyć
INTERSECT
/Queryable.Intersect
połączyć filtry. Często skutkuje to różnymi kształtami planów. - Utwórz wbudowaną funkcję z wartościami przechowywanymi w tabeli, która wykonuje filtrowanie. EF może użyć takiej funkcji jako części większego zapytania
- Opuść do surowego SQL
- Użyj przewodnika po planie, aby zmienić plan
Wszystkie te rozwiązania są obejściami, a nie poprawkami głównych przyczyn.
W końcu nie jestem zadowolony zarówno z SQL Server, jak i EF. Oba produkty powinny zostać naprawione. Niestety, prawdopodobnie nie będą i nie możesz na to czekać.
Oto skrypty indeksujące:
CREATE NONCLUSTERED INDEX IX_Widget_Colour_Size ON dbo.Widget
(
Colour, Size
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX IX_Widget_Size_Colour ON dbo.Widget
(
Size, Colour
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]