Modelowanie pod kątem wydajności

W wielu przypadkach sposób modelowania może mieć głęboki wpływ na wydajność aplikacji; chociaż prawidłowo znormalizowany i "poprawny" model jest zwykle dobrym punktem wyjścia, w rzeczywistych aplikacjach niektóre pragmatyczne kompromisy mogą przejść długą drogę do osiągnięcia dobrej wydajności. Ponieważ zmiana modelu po uruchomieniu aplikacji w środowisku produkcyjnym jest dość trudna, warto pamiętać o wydajności podczas tworzenia początkowego modelu.

Denormalizacja i buforowanie

Denormalizacja to praktyka dodawania nadmiarowych danych do schematu, zwykle w celu wyeliminowania sprzężeń podczas wykonywania zapytań. Na przykład w przypadku modelu z blogami i wpisami, w których każdy wpis ma wartość Ocena, może być konieczne częste wyświetlanie średniej oceny w blogu. Proste podejście do tego podejścia spowoduje zgrupowanie wpisów według bloga i obliczenie średniej w ramach zapytania; ale wymaga to kosztownego sprzężenia między dwiema tabelami. Denormalizacja spowoduje dodanie średniej obliczeniowej wszystkich wpisów do nowej kolumny w blogu, aby była natychmiast dostępna bez dołączania lub obliczania.

Powyższe informacje można wyświetlać jako formę buforowania — agregujące informacje z wpisów są buforowane w blogu. Podobnie jak w przypadku dowolnego buforowania, problem polega na tym, jak zachować buforowaną wartość na bieżąco z danymi, które są buforowane. W wielu przypadkach jest to ok, aby buforowane dane nieco opóźniły się; na przykład w powyższym przykładzie zwykle uzasadnione jest, aby średnia ocena w blogu nie była całkowicie aktualna w danym momencie. Jeśli tak jest, możesz go ponownie obliczyć co jakiś czas; W przeciwnym razie należy skonfigurować bardziej rozbudowany system, aby zapewnić aktualność buforowanych wartości.

Poniżej przedstawiono niektóre techniki denormalizacji i buforowania w programie EF Core oraz wskazuje odpowiednie sekcje w dokumentacji.

Przechowywane obliczone kolumny

Jeśli dane do buforowania są produktem innych kolumn w tej samej tabeli, przechowywana kolumna obliczeniowa może być idealnym rozwiązaniem. Na przykład kolumny Customer mogą zawierać FirstName kolumny i LastName , ale może być konieczne wyszukiwanie według pełnej nazwy klienta. Przechowywana kolumna obliczeniowa jest automatycznie utrzymywana przez bazę danych — co ponownie oblicza ją za każdym razem, gdy wiersz zostanie zmieniony — i można nawet zdefiniować indeks, aby przyspieszyć wykonywanie zapytań.

Aktualizowanie kolumn pamięci podręcznej w przypadku zmiany danych wejściowych

Jeśli buforowana kolumna musi odwoływać się do danych wejściowych spoza wiersza tabeli, nie można użyć obliczonych kolumn. Jednak nadal można ponownie obliczyć kolumnę za każdym razem, gdy zmienia się jego dane wejściowe; na przykład możesz ponownie obliczyć średnią ocenę w blogu za każdym razem, gdy wpis zostanie zmieniony, dodany lub usunięty. Pamiętaj, aby zidentyfikować dokładne warunki podczas ponownego obliczania, w przeciwnym razie buforowana wartość nie zostanie zsynchronizowana.

Jednym ze sposobów wykonania tej czynności jest samodzielne przeprowadzenie aktualizacji za pośrednictwem zwykłego interfejsu API platformy EF Core. SaveChangesZdarzenia lub przechwytniki mogą służyć do automatycznego sprawdzania, czy są aktualizowane wpisy, oraz do ponownego obliczania w ten sposób. Należy pamiętać, że zwykle wiąże się to z dodatkowymi przejazdami bazy danych, ponieważ należy wysłać dodatkowe polecenia.

Aby uzyskać więcej aplikacji poufnych dla wydajności, wyzwalacze bazy danych można zdefiniować w celu automatycznego ponownego obliczenia w bazie danych. Spowoduje to zapisanie dodatkowych pasków bazy danych, automatyczne wystąpienie w ramach tej samej transakcji co aktualizacja główna i może być prostsze do skonfigurowania. Platforma EF nie udostępnia żadnego konkretnego interfejsu API do tworzenia lub utrzymywania wyzwalaczy, ale doskonale nadaje się do tworzenia pustej migracji i dodawania definicji wyzwalacza za pomocą nieprzetworzonego kodu SQL.

Zmaterializowane/indeksowane widoki

Zmaterializowane (lub indeksowane) widoki są podobne do zwykłych widoków, z tą różnicą, że ich dane są przechowywane na dysku ("zmaterializowane"), a nie obliczane za każdym razem, gdy widok jest badany. Takie widoki są koncepcyjnie podobne do przechowywanych kolumn obliczeniowych, ponieważ buforują wyniki potencjalnie kosztownych obliczeń; jednak buforują zestaw wyników całego zapytania zamiast jednej kolumny. Zmaterializowane widoki mogą być wykonywane tak samo jak w przypadku każdej regularnej tabeli, a ponieważ są buforowane na dysku, takie zapytania są wykonywane bardzo szybko i tanio bez konieczności ciągłego wykonywania kosztownych obliczeń zapytania, które definiuje widok.

Konkretna obsługa zmaterializowanych widoków różni się w różnych bazach danych. W niektórych bazach danych (np. PostgreSQL) zmaterializowane widoki muszą być odświeżane ręcznie, aby ich wartości zostały zsynchronizowane z ich tabelami bazowymi. Zazwyczaj odbywa się to za pośrednictwem czasomierza — w przypadkach, gdy pewne opóźnienie danych jest akceptowalne — lub za pośrednictwem wyzwalacza lub wywołania procedury składowanej w określonych warunkach. Z drugiej strony widoki indeksowane programu SQL Server są automatycznie aktualizowane w miarę modyfikowania ich tabel bazowych. Zapewnia to, że widok zawsze wyświetla najnowsze dane kosztem wolniejszych aktualizacji. Ponadto widoki indeksów programu SQL Server mają różne ograniczenia dotyczące tego, co obsługują; Aby uzyskać więcej informacji, zapoznaj się z dokumentacją.

Program EF nie udostępnia obecnie żadnego konkretnego interfejsu API do tworzenia lub obsługi widoków, zmaterializowanych/indeksowanych lub w inny sposób; ale doskonale nadaje się do utworzenia pustej migracji i dodania definicji widoku za pomocą nieprzetworzonego kodu SQL.

Mapowanie dziedziczenia

Zaleca się przeczytanie dedykowanej strony dotyczącej dziedziczenia przed kontynuowaniem tej sekcji.

Program EF Core obsługuje obecnie trzy techniki mapowania modelu dziedziczenia na relacyjną bazę danych:

  • Tabela na hierarchię (TPH), w której cała hierarchia klas platformy .NET jest mapowana na jedną tabelę bazy danych.
  • Tabela na typ (TPT), w której każdy typ w hierarchii platformy .NET jest mapowany na inną tabelę w bazie danych.
  • Typ tabeli na beton (TPC), w którym każdy typ betonowy w hierarchii platformy .NET jest mapowany na inną tabelę w bazie danych, gdzie każda tabela zawiera kolumny dla wszystkich właściwości odpowiedniego typu.

Wybór techniki mapowania dziedziczenia może mieć znaczący wpływ na wydajność aplikacji — zaleca się dokładne pomiary przed zatwierdzeniem wyboru.

Intuicyjnie TPT może wydawać się techniką "czystszą"; Oddzielna tabela dla każdego typu platformy .NET sprawia, że schemat bazy danych wygląda podobnie do hierarchii typów platformy .NET. Ponadto, ponieważ funkcja TPH musi reprezentować całą hierarchię w jednej tabeli, wiersze mają wszystkie kolumny niezależnie od typu, który faktycznie jest przechowywany w wierszu, a niepowiązane kolumny są zawsze puste i nieużywane. Oprócz tego, że wydaje się być "nieczystą" techniką mapowania, wielu uważa, że te puste kolumny zajmują dużo miejsca w bazie danych i mogą również zaszkodzić wydajności.

Napiwek

Jeśli system bazy danych go obsługuje (np. program SQL Server), rozważ użycie kolumn rozrzedzonych dla kolumn TPH, które będą rzadko wypełniane.

Jednak pomiar pokazuje, że TPT jest w większości przypadków niższą techniką mapowania z punktu widzenia wydajności; gdzie wszystkie dane w TPH pochodzą z jednej tabeli, zapytania TPT muszą łączyć wiele tabel, a sprzężenia są jednym z głównych źródeł problemów z wydajnością w relacyjnych bazach danych. Bazy danych zwykle również dobrze sobie radzą z pustymi kolumnami, a funkcje, takie jak rozrzedłe kolumny programu SQL Server, mogą jeszcze bardziej zmniejszyć obciążenie.

Funkcja TPC ma podobne właściwości wydajności do TPH, ale jest nieco wolniejsza podczas wybierania jednostek wszystkich typów, ponieważ obejmuje to kilka tabel. Jednak funkcja TPC naprawdę wyróżnia się podczas wykonywania zapytań dotyczących jednostek pojedynczego typu liścia — zapytanie używa tylko jednej tabeli i nie wymaga filtrowania.

Aby zapoznać się z konkretnym przykładem, zobacz ten test porównawczy, który konfiguruje prosty model z hierarchią 7-typową; 5000 wierszy jest rozstawionych dla każdego typu — łącznie 35000 wierszy — a test porównawczy po prostu ładuje wszystkie wiersze z bazy danych:

Metoda Średnia Błąd StdDev Gen 0 Pierwszej generacji Alokowane
TPH 149.0 ms 3.38 ms 9,80 ms 4000.0000 1000.0000 40 MB
TPT 312.9 ms 6.17 ms 10.81 ms 9000.0000 3000.0000 75 MB
TPC 158.2 ms 3.24 ms 8.88 ms 5000.0000 2000.0000 46 MB

Jak widać, TPH i TPC są znacznie bardziej wydajne niż TPT w tym scenariuszu. Należy pamiętać, że rzeczywiste wyniki zawsze zależą od wykonywanego zapytania i liczby tabel w hierarchii, więc inne zapytania mogą wykazywać inną lukę w wydajności; Zachęcamy do używania tego kodu porównawczego jako szablonu do testowania innych zapytań.