Wydajne wykonywanie zapytań
Wydajne wykonywanie zapytań to obszerny temat, który obejmuje tematy tak szeroko zakrojone jak indeksy, powiązane strategie ładowania jednostek i wiele innych. W tej sekcji opisano niektóre typowe motywy służące do szybszego wykonywania zapytań, a użytkownicy zwykle napotykają pułapki.
Prawidłowe używanie indeksów
Głównym czynnikiem decydującym o tym, czy zapytanie działa szybko, czy nie, jest to, czy będzie prawidłowo korzystać z indeksów w odpowiednich przypadkach: bazy danych są zwykle używane do przechowywania dużych ilości danych i zapytań, które przechodzą przez całe tabele, są zwykle źródłami poważnych problemów z wydajnością. Problemy z indeksowaniem nie są łatwe do wykrycia, ponieważ nie jest od razu oczywiste, czy dana kwerenda będzie używać indeksu, czy nie. Przykład:
// Matches on start, so uses an index (on SQL Server)
var posts1 = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
// Matches on end, so does not use the index
var posts2 = context.Posts.Where(p => p.Title.EndsWith("A")).ToList();
Dobrym sposobem na wykrycie problemów z indeksowaniem jest najpierw wskazanie powolnego zapytania, a następnie sprawdzenie planu zapytania za pomocą ulubionego narzędzia bazy danych; Aby uzyskać więcej informacji na temat tego, jak to zrobić, zobacz stronę diagnostyki wydajności. Plan zapytania wyświetla, czy zapytanie przechodzi przez całą tabelę, czy używa indeksu.
Ogólnie rzecz biorąc, nie ma żadnej specjalnej wiedzy ef do korzystania z indeksów ani diagnozowania problemów z wydajnością związanych z nimi; ogólna wiedza na temat bazy danych związana z indeksami jest równie istotna dla aplikacji EF, jak w przypadku aplikacji, które nie korzystają z platformy EF. Poniżej wymieniono niektóre ogólne wytyczne, które należy wziąć pod uwagę podczas korzystania z indeksów:
- Podczas gdy indeksy przyspieszają zapytania, spowalniają również aktualizacje, ponieważ muszą być aktualne. Unikaj definiowania indeksów, które nie są potrzebne, i rozważ użycie filtrów indeksu , aby ograniczyć indeks do podzbioru wierszy, zmniejszając tym samym obciążenie.
- Indeksy złożone mogą przyspieszyć zapytania, które filtrują wiele kolumn, ale mogą również przyspieszyć zapytania, które nie filtrują wszystkich kolumn indeksu — w zależności od kolejności. Na przykład indeks kolumn A i B przyspiesza filtrowanie zapytań według A i B, a także zapytania filtrowania tylko według A, ale nie przyspiesza tylko filtrowania zapytań za pośrednictwem B.
- Jeśli zapytanie filtruje według wyrażenia w kolumnie (np.
price / 2
), nie można użyć prostego indeksu. Można jednak zdefiniować przechowywaną kolumnę utrwalone dla wyrażenia i utworzyć indeks. Niektóre bazy danych obsługują również indeksy wyrażeń, które mogą być używane bezpośrednio do przyspieszania filtrowania zapytań według dowolnego wyrażenia. - Różne bazy danych umożliwiają konfigurowanie indeksów na różne sposoby, a w wielu przypadkach dostawcy platformy EF Core udostępniają je za pośrednictwem interfejsu API Fluent. Na przykład dostawca programu SQL Server umożliwia skonfigurowanie, czy indeks jest klasterowany, czy też ustawiany jest jego współczynnik wypełnienia. Aby uzyskać więcej informacji, zapoznaj się z dokumentacją dostawcy.
Tylko potrzebne właściwości projektu
Program EF Core ułatwia wykonywanie zapytań dotyczących wystąpień jednostek, a następnie używanie tych wystąpień w kodzie. Jednak wykonywanie zapytań dotyczących wystąpień jednostek może często pobierać więcej danych niż jest to konieczne z bazy danych. Zaleca się uwzględnić następujące elementy:
foreach (var blog in context.Blogs)
{
Console.WriteLine("Blog: " + blog.Url);
}
Mimo że ten kod wymaga tylko właściwości każdego bloga Url
, cała jednostka bloga jest pobierana, a niepotrzebne kolumny są przenoszone z bazy danych:
SELECT [b].[BlogId], [b].[CreationDate], [b].[Name], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
Można to zoptymalizować za pomocą polecenia Select
ef, które kolumny mają być projektowane:
foreach (var blogName in context.Blogs.Select(b => b.Url))
{
Console.WriteLine("Blog: " + blogName);
}
Wynikowy kod SQL ściąga tylko potrzebne kolumny:
SELECT [b].[Url]
FROM [Blogs] AS [b]
Jeśli chcesz projektować więcej niż jedną kolumnę, przeprojektuj typ anonimowy języka C# z żądanymi właściwościami.
Należy pamiętać, że ta technika jest bardzo przydatna w przypadku zapytań tylko do odczytu, ale sytuacja jest bardziej skomplikowana, jeśli trzeba zaktualizować pobrane blogi, ponieważ śledzenie zmian ef działa tylko z wystąpieniami jednostek. Istnieje możliwość wykonywania aktualizacji bez ładowania całych jednostek przez dołączenie zmodyfikowanego wystąpienia bloga i informowanie ef, które właściwości uległy zmianie, ale jest to bardziej zaawansowana technika, która może nie być tego warta.
Ogranicz rozmiar zestawu wyników
Domyślnie zapytanie zwraca wszystkie wiersze zgodne z jego filtrami:
var blogsAll = context.Posts
.Where(p => p.Title.StartsWith("A"))
.ToList();
Ponieważ liczba zwracanych wierszy zależy od rzeczywistych danych w bazie danych, nie można wiedzieć, ile danych zostanie załadowanych z bazy danych, ile pamięci zostanie zajęta przez wyniki i ile dodatkowego obciążenia zostanie wygenerowane podczas przetwarzania tych wyników (np. wysyłając je do przeglądarki użytkownika za pośrednictwem sieci). Co najważniejsze, testowe bazy danych często zawierają małe dane, dzięki czemu wszystko działa dobrze podczas testowania, ale problemy z wydajnością pojawiają się nagle, gdy zapytanie zacznie działać na rzeczywistych danych i zwracanych jest wiele wierszy.
W związku z tym zwykle warto zastanowić się nad ograniczeniem liczby wyników:
var blogs25 = context.Posts
.Where(p => p.Title.StartsWith("A"))
.Take(25)
.ToList();
Co najmniej interfejs użytkownika może wyświetlić komunikat wskazujący, że w bazie danych może istnieć więcej wierszy (i zezwolić na ich pobieranie w inny sposób). Pełne rozwiązanie implementuje stronicowanie, w którym interfejs użytkownika wyświetla tylko określoną liczbę wierszy naraz i umożliwia użytkownikom przejście do następnej strony zgodnie z potrzebami. Zobacz następną sekcję, aby uzyskać więcej informacji na temat efektywnego implementowania tego rozwiązania.
Wydajne stronicowanie
Stronicowanie odnosi się do pobierania wyników na stronach, a nie wszystkich jednocześnie; Zazwyczaj jest to wykonywane w przypadku dużych zestawów wyników, w których jest wyświetlany interfejs użytkownika, który umożliwia użytkownikowi przejście do następnej lub poprzedniej strony wyników. Typowym sposobem implementacji stronicowania z bazami danych jest użycie Skip
operatorów i Take
(OFFSET
i LIMIT
w języku SQL), chociaż jest to intuicyjna implementacja, jest to również dość nieefektywne. W przypadku stronicowania, które umożliwia przenoszenie jednej strony naraz (w przeciwieństwie do przechodzenia do dowolnych stron), rozważ użycie stronicowania zestawu kluczy.
Aby uzyskać więcej informacji, zobacz stronę dokumentacji na stronie stronicowania.
Unikaj eksplozji kartezjańskiej podczas ładowania powiązanych jednostek
W relacyjnych bazach danych wszystkie powiązane jednostki są ładowane przez wprowadzenie numerów JOIN w jednym zapytaniu.
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]
Jeśli typowy blog zawiera wiele powiązanych wpisów, wiersze dla tych wpisów zduplikują informacje w blogu. Ta duplikacja prowadzi do tak zwanego problemu "wybuchu kartezjańskiego". W miarę ładowania większej liczby relacji jeden do wielu ilość zduplikowanych danych może rosnąć i niekorzystnie wpływać na wydajność aplikacji.
Program EF pozwala uniknąć tego efektu dzięki użyciu "podzielonych zapytań", które ładują powiązane jednostki za pośrednictwem oddzielnych zapytań. Aby uzyskać więcej informacji, przeczytaj dokumentację dotyczącą dzielenia i pojedynczych zapytań.
Uwaga
Bieżąca implementacja podzielonych zapytań wykonuje rundy dla każdego zapytania. Planujemy to poprawić w przyszłości i wykonać wszystkie zapytania w jednej rundzie.
Ładuj powiązane jednostki chętnie, gdy jest to możliwe
Zaleca się przeczytanie dedykowanej strony dotyczącej powiązanych jednostek przed kontynuowaniem pracy z tą sekcją.
W przypadku czynienia z powiązanymi jednostkami zwykle wiemy z wyprzedzeniem, co musimy załadować: typowy przykład polega na załadowaniu określonego zestawu blogów wraz ze wszystkimi wpisami. W tych scenariuszach zawsze lepiej jest używać chętnego ładowania, aby program EF mógł pobrać wszystkie wymagane dane w jednej rundzie. Filtrowana funkcja dołączania umożliwia również ograniczenie powiązanych jednostek, które chcesz załadować, przy jednoczesnym zachowaniu chętnego procesu ładowania i w związku z tym możliwego do wykonania w ramach pojedynczej rundy:
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(
blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
W innych scenariuszach możemy nie wiedzieć, której powiązanej jednostki będziemy potrzebować, zanim uzyskamy jej jednostkę główną. Na przykład podczas ładowania niektórych blogów może być konieczne skonsultowanie się z innym źródłem danych — prawdopodobnie usługą internetową — aby dowiedzieć się, czy jesteśmy zainteresowani wpisami w blogu. W takich przypadkach jawnelub leniwe ładowanie może służyć do pobierania powiązanych jednostek oddzielnie i wypełniania nawigacji Wpisy w blogu. Należy pamiętać, że ponieważ te metody nie są chętne, wymagają dodatkowych rund do bazy danych, co jest źródłem spowolnienia; w zależności od konkretnego scenariusza może być bardziej wydajne, aby zawsze ładować wszystkie wpisy, a nie wykonywać dodatkowych rund i selektywnie pobierać tylko potrzebne wpisy.
Uważaj na leniwe ładowanie
Ładowanie leniwe często wydaje się bardzo przydatnym sposobem na napisanie logiki bazy danych, ponieważ program EF Core automatycznie ładuje powiązane jednostki z bazy danych, gdy są one używane przez kod. Pozwala to uniknąć ładowania powiązanych jednostek, które nie są potrzebne (na przykład jawne ładowanie), i pozornie zwalnia programistę z konieczności całkowitego radzenia sobie z powiązanymi jednostkami. Jednak leniwe ładowanie jest szczególnie podatne na produkcję niepotrzebnych dodatkowych rund, które mogą spowolnić aplikację.
Zaleca się uwzględnić następujące elementy:
foreach (var blog in context.Blogs.ToList())
{
foreach (var post in blog.Posts)
{
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
}
}
Ten pozornie niewinny fragment kodu iteruje przez wszystkie blogi i ich wpisy, drukując je. Włączenie rejestrowania instrukcji platformy EF Core powoduje wyświetlenie następujących informacji:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [b].[BlogId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__p_0='2'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__p_0='3'], CommandType='Text', CommandTimeout='30']
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Post] AS [p]
WHERE [p].[BlogId] = @__p_0
... and so on
Co się tu dzieje? Dlaczego wszystkie te zapytania są wysyłane dla powyższych prostych pętli? Z opóźnieniem ładowania wpisy w blogu są ładowane tylko (leniwie), gdy jej właściwość Posts jest dostępna; W związku z tym każda iteracja w wewnętrznym foreach wyzwala dodatkowe zapytanie bazy danych w ramach własnej rundy. W związku z tym po początkowym zapytaniu ładujący wszystkie blogi mamy kolejne zapytanie na blog, ładujący wszystkie jego wpisy; jest to czasami nazywane problemem N+1 i może to spowodować bardzo znaczące problemy z wydajnością.
Zakładając, że będziemy potrzebować wszystkich wpisów blogów, warto zamiast tego użyć chętnego ładowania tutaj. Możemy użyć operatora Dołączanie , aby wykonać ładowanie, ale ponieważ potrzebujemy tylko adresów URL blogów (i powinniśmy załadować tylko to, co jest potrzebne). Użyjemy więc projekcji:
foreach (var blog in context.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
{
foreach (var post in blog.Posts)
{
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
}
}
Spowoduje to pobranie wszystkich blogów w programie EF Core — wraz z ich wpisami — w jednym zapytaniu. W niektórych przypadkach może być również przydatne, aby uniknąć efektów eksplozji kartezjańskiej przy użyciu podzielonych zapytań.
Ostrzeżenie
Ponieważ ładowanie leniwe sprawia, że niezwykle łatwo przypadkowo wyzwolić problem N+1, zaleca się go uniknąć. Załadowanie chętne lub jawne sprawia, że w kodzie źródłowym jest bardzo jasne, gdy wystąpi roundtrip bazy danych.
Buforowanie i przesyłanie strumieniowe
Buforowanie odnosi się do ładowania wszystkich wyników zapytania do pamięci, natomiast przesyłanie strumieniowe oznacza, że program EF przekazuje aplikacji jeden wynik za każdym razem, nigdy nie zawierający całego zestawu wyników w pamięci. Zasadniczo wymagania dotyczące pamięci zapytania przesyłania strumieniowego są stałe — są takie same, czy zapytanie zwraca 1 wiersz, czy 1000; z drugiej strony zapytanie buforujące wymaga większej ilości pamięci, tym więcej wierszy jest zwracanych. W przypadku zapytań, które powodują duże zestawy wyników, może to być ważny czynnik wydajności.
Czy bufory zapytania lub strumienie zależą od sposobu jego oceny:
// ToList and ToArray cause the entire resultset to be buffered:
var blogsList = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
var blogsArray = context.Posts.Where(p => p.Title.StartsWith("A")).ToArray();
// Foreach streams, processing one row at a time:
foreach (var blog in context.Posts.Where(p => p.Title.StartsWith("A")))
{
// ...
}
// AsEnumerable also streams, allowing you to execute LINQ operators on the client-side:
var doubleFilteredBlogs = context.Posts
.Where(p => p.Title.StartsWith("A")) // Translated to SQL and executed in the database
.AsEnumerable()
.Where(p => SomeDotNetMethod(p)); // Executed at the client on all database results
Jeśli zapytania zwracają tylko kilka wyników, prawdopodobnie nie musisz się tym martwić. Jeśli jednak zapytanie może zwrócić dużą liczbę wierszy, warto rozważyć przesyłanie strumieniowe zamiast buforowania.
Uwaga
Unikaj używania ToList lub ToArray jeśli zamierzasz użyć innego operatora LINQ w wyniku — będzie to niepotrzebnie buforować wszystkie wyniki w pamięci. Użycie w zamian parametru AsEnumerable.
Buforowanie wewnętrzne przez ef
W niektórych sytuacjach program EF wewnętrznie buforuje zestaw wyników niezależnie od sposobu oceny zapytania. Dwa przypadki, w których tak się dzieje, to:
- Po ponowieniu strategii wykonywania. Jest to wykonywane, aby upewnić się, że te same wyniki są zwracane, jeśli zapytanie zostanie ponowione później.
- Gdy jest używane zapytanie podzielone, zestawy wyników wszystkich, ale ostatnie zapytanie są buforowane — chyba że usługa MARS (wiele aktywnych zestawów wyników) jest włączona w programie SQL Server. Jest to spowodowane tym, że zwykle nie można jednocześnie uaktywnić wielu zestawów wyników zapytań.
Należy pamiętać, że to wewnętrzne buforowanie występuje oprócz buforowania, które jest przyczyną za pośrednictwem operatorów LINQ. Jeśli na przykład używasz ToList zapytania, a strategia ponawiania wykonywania zostanie załadowana do pamięci dwa razy: raz wewnętrznie przez program EF, a raz za ToListpomocą polecenia .
Śledzenie, brak śledzenia i rozpoznawanie tożsamości
Zaleca się przeczytanie dedykowanej strony na temat śledzenia i braku śledzenia przed kontynuowaniem tej sekcji.
Program EF domyślnie śledzi wystąpienia jednostek, dzięki czemu zmiany w nich są wykrywane i utrwalane podczas SaveChanges wywoływanych wywołań. Innym efektem śledzenia zapytań jest to, że program EF wykrywa, czy wystąpienie zostało już załadowane dla danych, i automatycznie zwróci to śledzone wystąpienie zamiast zwracać nowe; jest to nazywane rozpoznawaniem tożsamości. Z perspektywy wydajności śledzenie zmian oznacza następujące kwestie:
- Program EF wewnętrznie utrzymuje słownik śledzonych wystąpień. Po załadowaniu nowych danych program EF sprawdza słownik, aby sprawdzić, czy wystąpienie jest już śledzone pod kątem klucza tej jednostki (rozpoznawanie tożsamości). Konserwacja słownika i wyszukiwania zajmują trochę czasu podczas ładowania wyników zapytania.
- Przed przekazaniem załadowanego wystąpienia do aplikacji migawki ef tworzy migawki i przechowuje migawkę wewnętrznie. Po SaveChanges wywołaniu wystąpienie aplikacji jest porównywane z migawką w celu odnalezienia zmian, które mają być utrwalane. Migawka zajmuje więcej pamięci, a sam proces migawek zajmuje trochę czasu; Czasami można określić różne, prawdopodobnie bardziej wydajne zachowanie migawek za pośrednictwem porównań wartości lub użyć serwerów proxy śledzenia zmian w celu całkowitego obejścia procesu migawek (choć jest to własny zestaw wad).
W scenariuszach tylko do odczytu, w których zmiany nie są zapisywane z powrotem w bazie danych, powyższe obciążenia można uniknąć przy użyciu zapytań bez śledzenia. Jednak ponieważ zapytania bez śledzenia nie wykonują rozpoznawania tożsamości, wiersz bazy danych, do którego odwołuje się wiele innych załadowanych wierszy, zostanie zmaterializowany jako różne wystąpienia.
Aby zilustrować, załóżmy, że ładujemy dużą liczbę wpisów z bazy danych, a także blog, do których odwołuje się każdy wpis. Jeśli 100 wpisów odwołuje się do tego samego bloga, zapytanie śledzenia wykryje to za pośrednictwem rozpoznawania tożsamości, a wszystkie wystąpienia post będą odwoływać się do tego samego nieduplikowanego wystąpienia bloga. Natomiast zapytanie bez śledzenia duplikuje ten sam blog 100 razy — a odpowiednio należy napisać kod aplikacji.
Poniżej przedstawiono wyniki testu porównawczego porównującego śledzenie w porównaniu z zachowaniem braku śledzenia dla zapytania ładującej 10 blogów z 20 wpisami. Kod źródłowy jest dostępny tutaj, możesz używać go jako podstawy do własnych pomiarów.
Metoda | Dzienniki numerów | NumPostsPerBlog | Średnia | Błąd | StdDev | Mediana | Współczynnik | RatioSD | Gen 0 | Pierwszej generacji | Drugiej generacji | Alokowane |
---|---|---|---|---|---|---|---|---|---|---|---|---|
AsTracking | 10 | 20 | 1414.7 | 27.20 | 45.44 nas | 1405,5 nas | 1,00 | 0.00 | 60.5469 | 13.6719 | - | 380.11 KB |
AsNoTracking | 10 | 20 | 993.3 nas | 24.04 us | 65.40 nas | 966.2 nas | 0.71 | 0.05 | 37.1094 | 6.8359 | - | 232.89 KB |
Na koniec można wykonywać aktualizacje bez konieczności śledzenia zmian, używając zapytania bez śledzenia, a następnie dołączając zwrócone wystąpienie do kontekstu, określając, które zmiany mają zostać wprowadzone. Spowoduje to przeniesienie obciążenia śledzenia zmian z ef do użytkownika i powinno być podejmowane tylko wtedy, gdy obciążenie śledzenia zmian zostało wykazane jako niedopuszczalne za pośrednictwem profilowania lub porównywania porównawczego.
Korzystanie z zapytań SQL
W niektórych przypadkach bardziej zoptymalizowany język SQL istnieje dla zapytania, którego program EF nie generuje. Może się tak zdarzyć, gdy konstrukcja SQL jest rozszerzeniem specyficznym dla bazy danych, która nie jest obsługiwana, lub po prostu dlatego, że program EF nie tłumaczy się jeszcze na nią. W takich przypadkach pisanie kodu SQL ręcznie może zapewnić znaczny wzrost wydajności, a program EF obsługuje kilka sposobów, aby to zrobić.
- Użyj zapytań SQL bezpośrednio w zapytaniu, np. za pośrednictwem .FromSqlRaw Platforma EF umożliwia nawet tworzenie kodu SQL przy użyciu zwykłych zapytań LINQ, co umożliwia wyrażenie tylko części zapytania w języku SQL. Jest to dobra technika, gdy usługa SQL musi być używana tylko w jednym zapytaniu w bazie kodu.
- Zdefiniuj funkcję zdefiniowaną przez użytkownika (UDF), a następnie wywołaj tę funkcję z zapytań. Należy pamiętać, że funkcja EF umożliwia funkcji zdefiniowanych przez użytkownika zwracanie pełnych zestawów wyników — są one nazywane funkcjami o wartości tabeli (TVFs), a także umożliwia mapowanie
DbSet
elementu na funkcję, co sprawia, że wygląda tak samo jak inna tabela. - Zdefiniuj widok bazy danych i zapytanie z niego w zapytaniach. Należy pamiętać, że w przeciwieństwie do funkcji widoki nie mogą akceptować parametrów.
Uwaga
Nieprzetworzone dane SQL powinny być zwykle używane jako ostateczność, po upewnieniu się, że program EF nie może wygenerować żądanego kodu SQL, a gdy wydajność jest wystarczająco ważna dla danego zapytania, aby je uzasadnić. Korzystanie z nieprzetworzonego kodu SQL przynosi znaczne wady konserwacji.
Programowanie asynchroniczne
Ogólnie rzecz biorąc, aby aplikacja była skalowalna, ważne jest, aby zawsze używać asynchronicznych interfejsów API, a nie synchronicznych (np. SaveChangesAsyncSaveChanges). Synchroniczne interfejsy API blokują wątek przez czas trwania operacji we/wy bazy danych, zwiększając zapotrzebowanie na wątki i liczbę przełączników kontekstu wątku, które muszą wystąpić.
Aby uzyskać więcej informacji, zobacz stronę dotyczącą programowania asynchronicznego.
Ostrzeżenie
Unikaj mieszania synchronicznego i asynchronicznego kodu w tej samej aplikacji — bardzo łatwo jest przypadkowo wyzwalać subtelne problemy z głodem puli wątków.
Ostrzeżenie
Implementacja asynchronicznego elementu Microsoft.Data.SqlClient niestety ma pewne znane problemy (np. #593, #601 i inne). Jeśli występują nieoczekiwane problemy z wydajnością, spróbuj zamiast tego użyć polecenia synchronizacji, szczególnie w przypadku obsługi dużych wartości tekstowych lub binarnych.
Dodatkowe zasoby
- Aby uzyskać dodatkowe tematy dotyczące wydajnego wykonywania zapytań, zobacz stronę tematu zaawansowanego wydajności.
- Zapoznaj się z sekcją dotyczącą wydajności na stronie dokumentacji porównania wartości null, aby zapoznać się z najlepszymi rozwiązaniami dotyczącymi porównywania wartości null.