Udostępnij za pomocą


Przykładowa baza danych dla OLTP na potrzeby pamięci

Dotyczy:programu SQL ServerAzure SQL Database

Przegląd

W tym przykładzie przedstawiono funkcję OLTP w pamięci. Przedstawia tabele zoptymalizowane pod kątem pamięci oraz natywnie skompilowane procedury składowane, i może być używany do zademonstrowania korzyści wydajnościowych In-Memory OLTP.

Notatka

Aby wyświetlić ten artykuł dla programu SQL Server 2014 (12.x), zobacz Rozszerzenia dla AdventureWorks do demonstrowania In-Memory OLTP.

Przykład migruje pięć tabel w bazie danych AdventureWorks2025 na zoptymalizowane pod kątem pamięci i zawiera przykładowe obciążenie przetwarzania zamówień sprzedażowych. Możesz użyć tego obciążenia demonstracyjnego, aby zobaczyć korzyści z wydajności korzystania z olTP w pamięci na serwerze.

W opisie przykładu omówimy kompromisy, które zostały wykonane podczas migracji tabel do in-memory OLTP, aby uwzględnić funkcje, które nie są jeszcze () obsługiwane w przypadku tabel zoptymalizowanych pod kątem pamięci.

Dokumentacja tego przykładu jest ustrukturyzowana w następujący sposób:

Warunki wstępne

  • SQL Server 2016 (13.x)

  • Na potrzeby testowania wydajnościowego serwer ze specyfikacjami podobnymi do środowiska produkcyjnego. W przypadku tego konkretnego przykładu należy mieć co najmniej 16 GB pamięci dostępnej dla programu SQL Server. Ogólne wskazówki dotyczące sprzętu dla OLTP w pamięci można znaleźć w następującym wpisie w blogu: Zagadnienia sprzętowe dla In-Memory OLTP w SQL Server

Zainstaluj przykład OLTP w pamięci, bazując na AdventureWorks

Wykonaj następujące kroki, aby zainstalować próbkę:

  1. Pobierz AdventureWorks2016_EXT.bak i SQLServer2016Samples.zip z: https://github.com/microsoft/sql-server-samples/releases/tag/adventureworks do folderu lokalnego, na przykład C:\Temp.

  2. Przywróć kopię zapasową bazy danych przy użyciu programu Transact-SQL lub PROGRAMU SQL Server Management Studio:

    1. Zidentyfikuj folder docelowy i nazwę pliku danych, na przykład:

      H:\DATA\AdventureWorks2022_Data.mdf
      
    2. Zidentyfikuj folder docelowy i nazwę pliku dziennika, na przykład:

      I:\DATA\AdventureWorks2022_log.ldf
      
      1. Plik dziennika powinien zostać umieszczony na innym dysku niż plik danych, najlepiej w przypadku dysku o małym opóźnieniu, takim jak dysk SSD lub magazyn PCIe, w celu uzyskania maksymalnej wydajności.

    Przykładowy skrypt języka T-SQL:

    RESTORE DATABASE [AdventureWorks2022]
      FROM DISK = N'C:\temp\AdventureWorks2022.bak'
        WITH FILE = 1,
      MOVE N'AdventureWorks2022_Data' TO N'h:\DATA\AdventureWorks2022_Data.mdf',
      MOVE N'AdventureWorks2022_Log' TO N'i:\DATA\AdventureWorks2022_log.ldf',
      MOVE N'AdventureWorks2022_mod' TO N'h:\data\AdventureWorks2022_mod'
     GO
    
  3. Aby wyświetlić przykładowe skrypty i obciążenie, rozpakuj plik SQLServer2016Samples.zip do folderu lokalnego. Zapoznaj się z plikiem In-Memory OLTP\readme.txt , aby uzyskać instrukcje dotyczące uruchamiania obciążenia.

Opis przykładowych tabel i procedur

Przykład tworzy nowe tabele dla produktów i zamówień sprzedaży na podstawie istniejących tabel w AdventureWorks2025. Schemat nowych tabel jest podobny do istniejących tabel, z kilkoma różnicami, jak wyjaśniono w dalszej części tej sekcji.

Nowe tabele zoptymalizowane pod kątem pamięci zawierają sufiks _inmem. Przykład zawiera również odpowiednie tabele zawierające sufiks _ondisk — te tabele mogą służyć do porównania jednej do jednego między wydajnością tabel zoptymalizowanych pod kątem pamięci i tabel opartych na dyskach w systemie.

Tabele zoptymalizowane pod względem zużycia pamięci, używane do porównania wydajności obciążeń, są w pełni trwałe i w pełni dziennikowane. Nie poświęcają trwałości ani niezawodności, aby osiągnąć wzrost wydajności.

Obciążeniem docelowym dla tego przykładu jest przetwarzanie zamówień sprzedaży, w którym rozważamy również informacje o produktach i rabatach. W tym celu użyjemy tabel SalesOrderHeader, SalesOrderDetail, Product, SpecialOfferi SpecialOfferProduct.

Dwie nowe procedury składowane, Sales.usp_InsertSalesOrder_inmem i Sales.usp_UpdateSalesOrderShipInfo_inmem, są używane do wstawiania zamówień sprzedaży oraz aktualizowania informacji o wysyłce danego zamówienia sprzedaży.

Nowy schemat Demo zawiera tabele pomocnicze i procedury składowane do wykonania demonstracyjnego obciążenia.

Przykład In-Memory OLTP dodaje następujące obiekty do AdventureWorks2025:

Tabele dodane przez próbkę

Nowe tabele

Sales.SalesOrderHeader_inmem

  • Informacje nagłówkowe dotyczące zamówień sprzedaży. Każde zamówienie sprzedaży ma jeden wiersz w tej tabeli.

Sales.SalesOrderDetail_inmem

  • Szczegóły zamówień sprzedaży. Każda pozycja zamówienia sprzedaży ma jeden wiersz w tej tabeli.

Sales.SpecialOffer_inmem

  • Informacje o ofertach specjalnych, w tym procent rabatu skojarzony z każdą ofertą specjalną.

Sales.SpecialOfferProduct_inmem

  • Tabela referencyjna między ofertami specjalnymi i produktami. Każda oferta specjalna może zawierać zero lub więcej produktów, a każdy produkt może być opisywany w zero lub więcej ofert specjalnych.

Production.Product_inmem

  • Informacje o produktach, w tym ich cenniku.

Demo.DemoSalesOrderDetailSeed

  • Używane w demonstracyjnym obciążeniu roboczym do tworzenia przykładowych zleceń sprzedaży.

Odmiany tabel opartych na dyskach:

  • Sales.SalesOrderHeader_ondisk

  • Sales.SalesOrderDetail_ondisk

  • Sales.SpecialOffer_ondisk

  • Sales.SpecialOfferProduct_ondisk

  • Production.Product_ondisk

Różnice między oryginalnymi tabelami opartymi na dyskach i nowymi tabelami zoptymalizowanymi pod kątem pamięci

Zazwyczaj nowe tabele wprowadzone w tym przykładzie używają tych samych kolumn i tych samych typów danych co oryginalne tabele. Istnieje jednak kilka różnic. W tej sekcji wymieniono różnice wraz z uzasadnieniem zmian.

Sales.SalesOrderHeader_inmem

  • Domyślne ograniczenia są obsługiwane dla tabel zoptymalizowanych pod kątem pamięci, a większość z nich została zmigrowana bez zmian. Jednak oryginalna tabela Sales.SalesOrderHeader zawiera dwa domyślne ograniczenia, które pobierają bieżącą datę, dla kolumn OrderDate i ModifiedDate. W obciążeniu przetwarzania zamówień o wysokiej przepływności z dużą współbieżnością każdy zasób globalny może stać się punktem rywalizacji. Czas systemowy jest globalnym zasobem, i zaobserwowaliśmy, że może stać się wąskim gardłem w przypadku uruchamiania obciążeń In-Memory OLTP, które wstawiają zamówienia sprzedaży, zwłaszcza jeśli czas systemowy musi być pobrany dla wielu kolumn w nagłówku zamówienia sprzedaży oraz w szczegółach tych zamówień. Problem został poruszony w tym przykładzie poprzez pobranie czasu systemowego tylko raz dla każdego wstawionego zamówienia sprzedaży i użycie tej samej wartości dla kolumn daty i godziny w SalesOrderHeader_inmem i SalesOrderDetail_inmemw procedurze składowanej Sales.usp_InsertSalesOrder_inmem.

  • Aliasowe zdefiniowane przez użytkownika typy danych (UTD) — oryginalna tabela używa dwóch aliasów UTD, odpowiednio dbo.OrderNumber i dbo.AccountNumber, dla kolumn PurchaseOrderNumber i AccountNumber. Program SQL Server 2016 (13.x) nie obsługuje aliasu UDT dla tabel zoptymalizowanych pod kątem pamięci, dlatego nowe tabele używają odpowiednio typów danych systemowych nvarchar(25) i nvarchar(15).

  • Kolumny dopuszczane do wartości null w kluczach indeksu — w oryginalnej tabeli kolumna SalesPersonID jest dopuszczana do wartości null, natomiast w nowych tabelach kolumna nie jest dopuszczana do wartości null i ma domyślne ograniczenie wartości (-1). Jest to spowodowane tym, że indeksy w tabelach zoptymalizowanych dla pamięci nie mogą mieć kolumn, które mogą przyjmować wartość null w kluczu indeksu; -1 jest zastępcą wartości NULL w tym przypadku.

  • Obliczone kolumny — obliczone kolumny SalesOrderNumber i TotalDue są pomijane, ponieważ program SQL Server 2016 (13.x) nie obsługuje kolumn obliczanych w tabelach zoptymalizowanych pod kątem pamięci. Nowy widok Sales.vSalesOrderHeader_extended_inmem odzwierciedla kolumny SalesOrderNumber i TotalDue. W związku z tym można użyć tego widoku, jeśli te kolumny są potrzebne.

    • Dotyczy: SQL Server 2017 (14.x). Począwszy od programu SQL Server 2017 (14.x), obliczone kolumny są obsługiwane w tabelach i indeksach zoptymalizowanych pod kątem pamięci.
  • ograniczenia klucza obcego są obsługiwane w przypadku tabel zoptymalizowanych pod kątem pamięci w programie SQL Server 2016 (13.x), ale tylko wtedy, gdy przywoływane tabele są również zoptymalizowane pod kątem pamięci. Klucze obce, które odwołują się do tabel migrowanych do zoptymalizowanych pod kątem pamięci, są przechowywane w migrowanych tabelach, podczas gdy inne klucze obce są pomijane. Ponadto SalesOrderHeader_inmem jest gorącą tabelą w przykładowym obciążeniu, a ograniczenia kluczy obcych wymagają dodatkowego przetwarzania dla wszystkich operacji DML, ponieważ wymaga przeszukiwania we wszystkich innych tabelach, do których odwołują się te ograniczenia. W związku z tym zakłada się, że aplikacja zapewnia integralność referencyjną dla Sales.SalesOrderHeader_inmem tabeli, a integralność referencyjna nie jest weryfikowana podczas wstawiania wierszy.

  • rowguid — pominięto kolumnę rowguid. Chociaż funkcja uniqueidentifier jest obsługiwana w przypadku tabel zoptymalizowanych pod kątem pamięci, opcja ROWGUIDCOL nie jest obsługiwana w programie SQL Server 2016 (13.x). Kolumny tego rodzaju są zwykle używane albo do replikacji mieszanej, albo w tabelach posiadających kolumny typu filestream. Ten przykład nie zawiera żadnego z tych elementów.

Sprzedaż.SzczegółyZamówieniaSprzedaży

  • Domyślne ograniczenia — podobne do SalesOrderHeader, domyślne ograniczenie wymagające daty/godziny systemowej nie jest migrowane. Zamiast tego procedura składowana wstawiająca zamówienia sprzedaży zajmuje się wstawianiem bieżącej daty/godziny systemu przy pierwszym wstawieniu.

  • Obliczone kolumny — obliczona kolumna LineTotal nie została zmigrowana, ponieważ obliczone kolumny nie są obsługiwane w przypadku tabel zoptymalizowanych pod kątem pamięci w programie SQL Server 2016 (13.x). Aby uzyskać dostęp do tej kolumny, użyj Sales.vSalesOrderDetail_extended_inmem widoku.

  • Rowguid — pominięto kolumnę rowguid. Aby uzyskać szczegółowe informacje, zobacz opis tabeli SalesOrderHeader.

Production.Product

  • aliasy UDT — oryginalna tabela używa typu danych zdefiniowanego przez użytkownika dbo.Flag, który jest odpowiednikiem typu danych systemowych bit. Zmigrowana tabela używa zamiast tego typu danych bitowych.

  • Rowguid — pominięto kolumnę rowguid. Aby uzyskać szczegółowe informacje, zobacz opis tabeli SalesOrderHeader.

Sprzedaż.SpecjalnaOferta

  • Rowguid — pominięto kolumnę rowguid. Aby uzyskać szczegółowe informacje, zobacz opis tabeli SalesOrderHeader.

Sprzedaż.OfertaSpecjalnaProdukt

  • Rowguid — pominięto kolumnę rowguid. Aby uzyskać szczegółowe informacje, zobacz opis tabeli SalesOrderHeader.

Zagadnienia dotyczące indeksów w tabelach zoptymalizowanych pod kątem pamięci

Podstawowy indeks dla tabel zoptymalizowanych pod kątem pamięci to indeks NONCLUSTERED, który obsługuje wyszukiwanie bezpośrednie (wyszukiwanie indeksu w predykacie równości), skanowania zakresów (wyszukiwanie indeksu przy użyciu predykatu nierówności), pełne skanowanie indeksu oraz uporządkowane skanowania. Ponadto indeksy NONCLUSTERED obsługują wyszukiwanie w kolumnach wiodących klucza indeksu. W rzeczywistości indeksy NONCLUSTERED zoptymalizowane pod kątem pamięci obsługują wszystkie operacje obsługiwane przez indeksy NONCLUSTERED oparte na dyskach, przy czym jedynym wyjątkiem jest skanowanie wsteczne. Dlatego użycie indeksów NONCLUSTERED jest bezpiecznym wyborem.

Indeksy haszujące mogą służyć do dalszej optymalizacji obciążenia. Są one zoptymalizowane pod kątem wyszukiwań punktowych i wstawiania wierszy. Należy jednak wziąć pod uwagę, że nie obsługują skanów zakresowych, skanów uporządkowanych ani wyszukiwania w wiodących kolumnach klucza indeksu. W związku z tym należy zachować ostrożność podczas korzystania z tych indeksów. Ponadto konieczne jest określenie bucket_count podczas tworzenia. Zwykle powinna być ustawiana między jedną a dwiema razy liczbą wartości klucza indeksu, ale przeszacowanie zwykle nie jest problemem.

Aby uzyskać więcej informacji:

Indeksy w zmigrowanych tabelach zostały dostosowane do obciążenia przetwarzania zamówień sprzedaży demonstracyjnej. Obciążenie opiera się na wstawianiu i wyszukiwaniu punktów w tabelach Sales.SalesOrderHeader_inmem i Sales.SalesOrderDetail_inmem, a także opiera się na wyszukiwaniach punktów w kolumnach klucza podstawowego w tabelach Production.Product_inmem i Sales.SpecialOffer_inmem.

Sales.SalesOrderHeader_inmem ma trzy indeksy skrótów dla wydajności oraz dlatego, że nie są potrzebne żadne uporządkowane ani zakresowe skanowania w ramach obciążenia.

  • Indeks HASH w dniu (SalesOrderID): bucket_count ma rozmiar 10 milionów (zaokrąglony do 16 milionów), ponieważ oczekiwana liczba zamówień sprzedaży wynosi 10 milionów

  • Indeks skrótu (SalesPersonID): bucket_count wynosi 1 milion. Dostarczony zestaw danych nie zawiera wielu przedstawicieli handlowych. Ale ten duży bucket_count pozwala na przyszły wzrost. Dodatkowo, nie ponosisz kary za wydajność przy wyszukiwaniach punktowych, jeśli bucket_count jest zbyt duża.

  • Indeks skrótu (CustomerID): bucket_count wynosi 1 milion. Podany zestaw danych nie ma wielu klientów, ale pozwala to na przyszły wzrost.

Sales.SalesOrderDetail_inmem ma trzy indeksy skrótów dla wydajności oraz dlatego, że nie są potrzebne żadne uporządkowane ani zakresowe skanowania w ramach obciążenia.

  • Indeks HASH dla (SalesOrderID, SalesOrderDetailID): jest to indeks klucza podstawowego, a mimo że wyszukiwania dla (SalesOrderID, SalesOrderDetailID) są rzadkie, użycie indeksu skrótu dla klucza przyspiesza wstawianie wierszy. Rozmiar bucket_count wynosi 50 milionów (zaokrąglony do 67 milionów): oczekiwana liczba zamówień sprzedaży wynosi 10 milionów, a wielkość ta wynosi średnio pięć pozycji na zamówienie

  • Indeks HASH na (SalesOrderID): wyszukiwania według zamówienia sprzedaży są częste: chcesz znaleźć wszystkie pozycje odpowiadające pojedynczemu zamówieniu. bucket_count ma rozmiar 10 milionów (zaokrąglony do 16 milionów), ponieważ oczekiwana liczba zamówień sprzedaży wynosi 10 milionów

  • Indeks skrótu (ProductID): bucket_count wynosi 1 milion. Podany zestaw danych nie zawiera wielu produktów, ale pozwala to na przyszły wzrost.

Production.Product_inmem ma trzy indeksy

  • Indeks skrótu na (ProductID): wyszukiwania w ProductID są w ścieżce krytycznej dla obciążenia demonstracyjnego, dlatego jest to indeks skrótu.

  • Indeks nieklastrowany na (Name): umożliwia to uporządkowane skanowanie nazw produktów

  • Indeks NONCLUSTERED na (ProductNumber): umożliwia uporządkowane skanowanie numerów produktów

Sales.SpecialOffer_inmem ma jeden indeks HASH na (SpecialOfferID): wyszukiwanie punktów ofert specjalnych znajduje się w krytycznej części obciążenia demonstracyjnego. bucket_count ma rozmiar wynoszący 1 milion, aby umożliwić przyszły wzrost.

Sales.SpecialOfferProduct_inmem nie jest wspomniane w obciążeniu demonstracyjnym, dlatego nie ma wyraźnej potrzeby używania indeksów haszujących w tej tabeli w celu zoptymalizowania obciążenia - indeksy na (SpecialOfferID, ProductID) i (ProductID) są nieklastrowane.

W poprzednim przykładzie niektóre z liczników zasobników są zbyt duże, ale liczby zasobników dla indeksów na SalesOrderHeader_inmem i SalesOrderDetail_inmem są odpowiednie: mają rozmiar tylko na 10 milionów zamówień sprzedaży. Zostało to zrobione w celu umożliwienia instalowania przykładu w systemach o niskiej dostępności pamięci, chociaż w takich przypadkach obciążenie demonstracyjne kończy się niepowodzeniem z powodu błędu braku pamięci. Jeśli chcesz skalować znacznie więcej niż 10 milionów zamówień sprzedaży, możesz odpowiednio zwiększyć liczbę zasobników.

Zagadnienia dotyczące wykorzystania pamięci

Wykorzystanie pamięci w przykładowej bazie danych, zarówno przed, jak i po uruchomieniu obciążenia demonstracyjnego, zostało omówione w sekcji wykorzystanie pamięci dla tabel zoptymalizowanych pod kątem pamięci.

Procedury składowane dodane przez przykładowy kod

Dwie kluczowe procedury składowane dotyczące dodawania zleceń sprzedaży i aktualizowania szczegółów wysyłki są następujące:

  • Sales.usp_InsertSalesOrder_inmem

    • Wprowadza nowe zamówienie sprzedaży do bazy danych i generuje numer SalesOrderID dla tego zamówienia sprzedaży. Jako parametry wejściowe pobiera szczegóły nagłówka zamówienia sprzedaży i elementy wiersza w zamówieniu.

    • Parametr wyjściowy:

      • @SalesOrderID int — wartość SalesOrderID dla zamówienia sprzedaży, które zostało właśnie wstawione
    • Parametry wejściowe (wymagane):

      • @DueDatedatetime2
      • @CustomerIDint
      • @BillToAddressIDint
      • @ShipToAddressIDint
      • @ShipMethodIDint
      • Sales.SalesOrderDetailType_inmem@SalesOrderDetails - parametr o wartości tabeli (TVP), który zawiera elementy wiersza zamówienia
    • Parametry wejściowe (opcjonalnie):

      • @Statustinyint
      • @OnlineOrderFlagbit
      • @PurchaseOrderNumbernvarchar(25)
      • @AccountNumbernvarchar(15)
      • @SalesPersonIDint
      • @TerritoryIDint
      • @CreditCardIDint
      • @CreditCardApprovalCodevarchar(15)
      • @CurrencyRateIDint
      • @Commentnvarchar(128)
  • Sales.usp_UpdateSalesOrderShipInfo_inmem

    • Zaktualizuj informacje o wysyłki dla danego zamówienia sprzedaży. Spowoduje to również zaktualizowanie informacji o wysyłce dla wszystkich elementów zamówienia sprzedaży.

    • Jest to procedura opakowująca dla natywnie skompilowanych procedur składowanych Sales.usp_UpdateSalesOrderShipInfo_native z logiką ponawiania, aby radzić sobie z nieoczekiwanymi potencjalnymi konfliktami z równoczesnymi transakcjami aktualizującymi to samo zamówienie. Aby uzyskać więcej informacji, zobacz logikę ponawiania prób.

  • Sales.usp_UpdateSalesOrderShipInfo_native

    • Jest to natywnie skompilowana procedura składowana, która rzeczywiście przetwarza aktualizację informacji o wysyłce. Jest przeznaczona do wywołania z procedury składowanej otoczki Sales.usp_UpdateSalesOrderShipInfo_inmem. Jeśli klient może radzić sobie z błędami i implementuje logikę ponawiania, możesz wywołać tę procedurę bezpośrednio, bez użycia procedury składowanej opakowania.

Poniższa procedura składowana jest używana dla obciążenia demonstracyjnego.

  • Demo.usp_DemoReset

    • Resetuje pokaz, opróżniając i ponownie wysyłając tabele SalesOrderHeader i SalesOrderDetail.

Poniższe procedury składowane są używane do dodawania do i usuwania z tabel zoptymalizowanych pod kątem pamięci, jednocześnie zapewniając integralność domenową i referencyjną.

  • Production.usp_InsertProduct_inmem
  • Production.usp_DeleteProduct_inmem
  • Sales.usp_InsertSpecialOffer_inmem
  • Sales.usp_DeleteSpecialOffer_inmem
  • Sales.usp_InsertSpecialOfferProduct_inmem

Na koniec poniższa procedura składowana służy do weryfikowania domeny i integralności referencyjnej.

  1. dbo.usp_ValidateIntegrity

    • Opcjonalny parametr: @object_id — identyfikator obiektu w celu zweryfikowania integralności dla

    • Ta procedura opiera się na tabelach dbo.DomainIntegrity, dbo.ReferentialIntegrityi dbo.UniqueIntegrity dla reguł integralności, które należy zweryfikować — przykład wypełnia te tabele na podstawie kontroli, klucza obcego i unikatowych ograniczeń istniejących dla oryginalnych tabel w bazie danych AdventureWorks2025.

    • Opiera się on na procedurach pomocnika dbo.usp_GenerateCKCheck, dbo.usp_GenerateFKChecki dbo.GenerateUQCheck generowania języka T-SQL potrzebnego do przeprowadzania kontroli integralności.

Pomiary wydajności przy użyciu obciążenia pokazowego

ostress to narzędzie wiersza polecenia, które zostało opracowane przez zespół pomocy technicznej programu Microsoft CSS SQL Server. To narzędzie może służyć do równoległego wykonywania zapytań lub uruchamiania procedur składowanych. Można skonfigurować liczbę wątków do równoległego uruchamiania danej instrukcji języka T-SQL i określić, ile razy instrukcja ma być wykonywana w tym wątku; ostress uruchamia wątki i wykonuje instrukcję na wszystkich wątkach równolegle. Po zakończeniu wykonywania wszystkich wątków ostress raportuje czas potrzebny na zakończenie ich wykonywania.

Instalowanie narzędzia ostress

program ostress jest instalowany jako część narzędzi języka RML (Report Markup Language); nie ma autonomicznej instalacji narzędzia ostress.

Kroki instalacji:

  1. Pobierz i uruchom pakiet instalacyjny x64 dla narzędzi RML z następującej strony: Pobierz narzędzia RML dla programu SQL Server

  2. Jeśli istnieje okno dialogowe z informacją, że niektóre pliki są używane, wybierz pozycję "Kontynuuj"

Uruchom narzędzie ostress

Ostress jest uruchamiany z wiersza polecenia. Najwygodniej jest uruchomić narzędzie z wiersza poleceń RML Cmd, który jest instalowany jako część narzędzi RML.

Aby otworzyć wiersz polecenia RML, wykonaj następujące instrukcje:

W systemie Windows otwórz menu Start, wybierając klucz systemu Windows i wpisz rml. Wybierz RML Cmd Prompt, które znajduje się na liście wyników wyszukiwania.

Upewnij się, że wiersz polecenia znajduje się w folderze instalacyjnym RML Utilities.

Opcje wiersza polecenia dla ostress można zobaczyć, po prostu uruchamiając ostress.exe bez żadnych opcji wiersza polecenia. Główne opcje, które należy wziąć pod uwagę podczas uruchamiania narzędzia ostress z tym przykładem, są następujące:

Option Description
-S Nazwa instancji serwera SQL, z którą należy nawiązać połączenie.
-E Użyj uwierzytelniania systemu Windows, aby nawiązać połączenie (ustawienie domyślne); Jeśli używasz uwierzytelniania programu SQL Server, użyj opcji -U i -P określ odpowiednio nazwę użytkownika i hasło.
-d Nazwa bazy danych, na potrzeby tego przykładu AdventureWorks2025.
-Q Instrukcja T-SQL do wykonania.
-n Liczba połączeń przetwarzających każdy plik wejściowy/zapytanie.
-r Liczba iteracji dla każdego połączenia w celu wykonania każdego pliku wejściowego/zapytania.

Pokazowe obciążenie

Główną procedurą składowaną używaną w obciążeniu demonstracyjnym jest Sales.usp_InsertSalesOrder_inmem/ondisk. Skrypt w poniższym przykładzie tworzy parametr tabelaryczny (TVP) z przykładowymi danymi i wywołuje procedurę w celu wstawienia zamówienia sprzedaży z pięcioma pozycjami.

Narzędzie ostress służy do równoległego wykonywania wywołań procedury składowanej, aby symulować klientów jednocześnie wstawiających zamówienia sprzedaży.

Zresetuj pokaz po każdym uruchomieniu obciążenia wykonującym Demo.usp_DemoReset. Ta procedura usuwa wiersze z tabel zoptymalizowanych pod kątem pamięci, przycina tabele oparte na dyskach i wykonuje punkt kontrolny bazy danych.

Następujący skrypt jest wykonywany współbieżnie w celu symulowania obciążenia przetwarzania zamówień sprzedaży:

DECLARE @i AS INT = 0, @od AS Sales.SalesOrderDetailType_inmem, @SalesOrderID AS INT, @DueDate AS DATETIME2 = sysdatetime(), @CustomerID AS INT = RAND() * 8000, @BillToAddressID AS INT = RAND() * 10000, @ShipToAddressID AS INT = RAND() * 10000, @ShipMethodID AS INT = (RAND() * 5) + 1;
INSERT INTO @od
SELECT OrderQty,
       ProductID,
       SpecialOfferID
FROM Demo.DemoSalesOrderDetailSeed
WHERE OrderID = CAST ((RAND() * 106) + 1 AS INT);
WHILE (@i < 20)
    BEGIN
        EXECUTE Sales.usp_InsertSalesOrder_inmem
            @SalesOrderID OUTPUT,
            @DueDate,
            @CustomerID,
            @BillToAddressID,
            @ShipToAddressID,
            @ShipMethodID,
            @od;
        SET @i + = 1;
    END

W przypadku tego skryptu każde skonstruowane zamówienie próbki jest wstawiane 20 razy, przez 20 procedur przechowywanych wykonywanych w pętli WHILE. Pętla służy do uwzględniania faktu, że baza danych jest używana do konstruowania przykładowej kolejności. W typowych środowiskach produkcyjnych aplikacja warstwy średniej konstruuje zamówienie sprzedaży, które ma być wprowadzone.

Poprzedni skrypt wstawia zamówienia sprzedaży do tabel zoptymalizowanych pod kątem pamięci. Skrypt do wstawiania zamówień sprzedaży do tabel opartych na dyskach jest tworzony poprzez zastąpienie dwóch wystąpień _inmem elementem _ondisk.

Używamy narzędzia ostress do wykonywania skryptów przy użyciu kilku współbieżnych połączeń. Używamy parametru -n do kontrolowania liczby połączeń i parametru r do kontrolowania, ile razy skrypt jest wykonywany na każdym połączeniu.

Uruchom obciążenie

Aby przetestować w dużej skali, wprowadzamy 10 milionów zamówień sprzedaży przy użyciu 100 połączeń. Ten test działa rozsądnie na skromnym serwerze (na przykład 8 rdzeni fizycznych, 16 rdzeni logicznych) i podstawowej pamięci SSD dla logu. Jeśli test nie działa dobrze na sprzęcie, zapoznaj się z sekcją Rozwiązywanie problemów z powolnymi testami. Jeśli chcesz zmniejszyć poziom obciążenia dla tego testu, zmniejsz liczbę połączeń, zmieniając parametr -n. Na przykład aby zmniejszyć liczbę połączeń do 40, zmień parametr -n100 na -n40.

Jako miara wydajności obciążenia używamy czasu, który upłynął zgodnie z raportem ostress.exe po uruchomieniu obciążenia.

Poniższe instrukcje i pomiary używają obciążenia roboczego, które wstawia 10 milionów zamówień sprzedaży. Aby uzyskać instrukcje dotyczące uruchamiania zmniejszonego obciążenia i wstawiania 1 miliona zamówień sprzedaży, zobacz instrukcje w In-Memory OLTP\readme.txt będące częścią archiwum SQLServer2016Samples.zip.

Tabele zoptymalizowane pod kątem pamięci

Zaczynamy od uruchomienia obciążenia w tabelach zoptymalizowanych pod kątem pamięci. Następujące polecenie otwiera 100 wątków, z których każdy wykonuje 5000 iteracji. Każda iteracja wstawia 20 zamówień sprzedaży w oddzielnych transakcjach. Istnieje 20 wstawek na iterację, aby zrekompensować fakt, że baza danych jest używana do generowania danych do wstawienia. Daje to łącznie 20 * 5000 * 100 = 10 000 000 wstawień zamówień sprzedaży.

Otwórz RML Cmd Prompt i wykonaj następujące polecenie:

Wybierz przycisk Kopiuj, aby skopiować polecenie i wkleić je do wiersza polecenia RML Utilities.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i AS INT = 0, @od AS Sales.SalesOrderDetailType_inmem, @SalesOrderID AS INT, @DueDate AS DATETIME2 = SYSDATETIME(), @CustomerID AS INT = RAND() * 8000, @BillToAddressID AS INT = RAND() * 10000, @ShipToAddressID AS INT = RAND() * 10000, @ShipMethodID AS INT = (RAND() * 5) + 1; INSERT INTO @od SELECT OrderQty, ProductID, SpecialOfferID FROM Demo.DemoSalesOrderDetailSeed WHERE OrderID = CAST ((RAND() * 106) + 1 AS INT); WHILE (@i < 20) BEGIN EXECUTE Sales.usp_InsertSalesOrder_inmem @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; SET @i + = 1; END"

Na jednym serwerze testowym z całkowitą liczbą 8 rdzeni fizycznych (16 logicznych) zajęło to 2 minuty i 5 sekund. Na drugim serwerze testowym z 24 rdzeniami fizycznymi (48 logicznymi) zajęło to 1 minutę i 0 sekund.

Obserwuj użycie procesora CPU, gdy obciążenie jest uruchomione, na przykład przy użyciu menedżera zadań. Zobaczysz, że użycie procesora CPU jest zbliżone do 100%. Jeśli tak nie jest, występuje wąskie gardło we/wy dziennika, zobacz Rozwiązywanie problemów z powolnym uruchamianiem testów.

Tabele oparte na dyskach

Następujące polecenie uruchamia obciążenie w tabelach opartych na dyskach. Wykonanie tego obciążenia może zająć trochę czasu, co w dużej mierze wynika z rywalizacji o zatrzaski w systemie. Tabele zoptymalizowane pod kątem pamięci są wolne od zatrzaśnięć i nie cierpią z powodu tego problemu.

Otwórz wiersz polecenia RML i wykonaj następujące polecenie:

Wybierz przycisk Kopiuj, aby skopiować polecenie i wkleić je do wiersza polecenia RML Utilities.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i AS INT = 0, @od AS Sales.SalesOrderDetailType_ondisk, @SalesOrderID AS INT, @DueDate AS DATETIME2 = sysdatetime(), @CustomerID AS INT = RAND() * 8000, @BillToAddressID AS INT = RAND() * 10000, @ShipToAddressID AS INT = RAND() * 10000, @ShipMethodID AS INT = (RAND() * 5) + 1; INSERT INTO @od SELECT OrderQty, ProductID, SpecialOfferID FROM Demo.DemoSalesOrderDetailSeed WHERE OrderID = CAST ((RAND() * 106) + 1 AS INT); WHILE (@i < 20) BEGIN EXECUTE Sales.usp_InsertSalesOrder_ondisk @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; SET @i + = 1; END"

Na jednym serwerze testowym z całkowitą liczbą 8 rdzeni fizycznych (16 rdzeni logicznych) zajęło to 41 minut i 25 sekund. Na drugim serwerze testowym z 24 rdzeniami fizycznymi (48 logicznymi) zajęło to 52 minuty i 16 sekund.

Głównym czynnikiem różnicy wydajności między tabelami zoptymalizowanym pod kątem pamięci i tabelami opartymi na dyskach w tym teście jest to, że w przypadku korzystania z tabel opartych na dyskach program SQL Server nie może w pełni wykorzystać procesora CPU. Przyczyną jest rywalizacja wynikająca z zatrzasków: transakcje współbieżne próbują zapisać na tej samej stronie danych; zatrzaski są stosowane, aby zapewnić, że tylko jedna transakcja naraz może zapisywać na stronie. Silnik OLTP In-Memory jest pozbawiony zatrzasków, a wiersze danych nie są zorganizowane w stronach. W związku z tym równoczesne transakcje nie blokują wzajemnych operacji wstawiania, co umożliwia programowi SQL Server pełne wykorzystanie procesora.

Możesz obserwować wykorzystanie procesora CPU, gdy obciążenie jest uruchomione, na przykład przy użyciu menedżera zadań. W przypadku tabel opartych na dyskach użycie procesora jest znacznie niższe niż 100%. W przypadku konfiguracji testowej z 16 procesorami logicznymi, wykorzystanie będzie się utrzymywać w okolicach 24%.

Opcjonalnie możesz wyświetlić liczbę oczekiwań na zatrzask na sekundę przy użyciu narzędzia Performance Monitor, z licznikiem wydajności \SQL Server:Latches\Latch Waits/sec.

Resetowanie dema

Aby zresetować demonstrację, otwórz wiersz polecenia RML i wykonaj następujące polecenie:

ostress.exe -S. -E -dAdventureWorks2022 -Q"EXEC Demo.usp_DemoReset"

W zależności od sprzętu może to potrwać kilka minut.

Zalecamy zresetowanie po każdym uruchomieniu demonstracji. Ponieważ to obciążenie dotyczy tylko wstawiania, każde uruchomienie zużywa więcej pamięci, więc reset jest wymagany, aby uniknąć wyczerpania pamięci. Ilość pamięci zużywanej po uruchomieniu została omówiona w sekcji wykorzystanie pamięci po uruchomieniu obciążenia.

Rozwiązywanie problemów z powolnymi testami

Wyniki testów zwykle różnią się w zależności od sprzętu, a także poziomu współbieżności używanej w przebiegu testu. Kilka rzeczy, na które należy zwrócić uwagę, jeżeli wyniki nie są zgodne z oczekiwaniami:

  • Liczba współbieżnych transakcji: Przy uruchamianiu obciążenia na jednym wątku, oczekiwany wzrost wydajności dzięki In-Memory OLTP jest prawdopodobnie mniejszy niż dwukrotny. Rywalizacja o opóźnienie jest tylko istotnym problemem, jeśli występuje wysoki poziom współbieżności.

  • Niska liczba rdzeni dostępnych dla programu SQL Server: oznacza to, że w systemie istnieje niski poziom współbieżności, ponieważ może istnieć tylko tyle współbieżnych transakcji, ile rdzeni jest dostępnych dla języka SQL.

    • Objaw: jeśli wykorzystanie procesora jest wysokie podczas uruchamiania obciążenia w tabelach opartych na dyskach, oznacza to, że nie ma dużych zatorów, co wskazuje na brak współbieżności.
  • Szybkość dysku dziennika: jeśli dysk dziennika nie może nadążyć za poziomem przepływności transakcji w systemie, obciążenie stanie się wąskim gardłem w przypadku operacji we/wy dziennika. Chociaż rejestrowanie jest bardziej wydajne w przypadku In-Memory OLTP, jeśli I/O dziennika jest wąskim gardłem, potencjalny wzrost wydajności jest ograniczony.

    • Objaw: jeśli wykorzystanie CPU nie jest zbliżone do 100% lub jest bardzo nieregularne podczas uruchamiania obciążenia na tabelach zoptymalizowanych dla pamięci, istnieje prawdopodobnie wąskie gardło operacji IO dziennika. Można to potwierdzić, otwierając monitor zasobów i sprawdzając długość kolejki dla dysku dziennika.

Wykorzystanie pamięci i miejsca na dysku w przykładzie

W poniższym przykładzie opisano, czego można oczekiwać w odniesieniu do wykorzystania pamięci i miejsca na dysku dla przykładowej bazy danych. Pokazujemy również wyniki z serwera testowego z 16 rdzeniami logicznymi.

Wykorzystanie pamięci dla tabel zoptymalizowanych pod kątem pamięci

Ogólne wykorzystanie bazy danych

Poniższe zapytanie może służyć do uzyskania całkowitego wykorzystania pamięci dla In-Memory OLTP w systemie.

SELECT type,
       name,
       pages_kb / 1024 AS pages_MB
FROM sys.dm_os_memory_clerks
WHERE type LIKE '%xtp%';

Migawka po utworzeniu bazy danych:

typ nazwa strony_MB
MEMORYCLERK_XTP Domyślny 94
MEMORYCLERK_XTP DB_ID_5 877
MEMORYCLERK_XTP Domyślny 0
MEMORYCLERK_XTP Domyślny 0

Domyślne urzędy pamięci zawierają struktury pamięci dla całego systemu i są stosunkowo małe. Zarządca pamięci dla bazy danych użytkownika, w tym przypadku bazy danych o identyfikatorze 5 (c0 może się różnić w twojej instancji), wynosi około 900 MB.

Wykorzystanie pamięci na tabelę

Następujące zapytanie może służyć do przechodzenia do szczegółów wykorzystania pamięci poszczególnych tabel i ich indeksów:

SELECT object_name(t.object_id) AS [Table name],
       memory_allocated_for_table_kb,
       memory_allocated_for_indexes_kb
FROM sys.dm_db_xtp_table_memory_stats AS dms
     INNER JOIN sys.tables AS t
         ON dms.object_id = t.object_id
WHERE t.type = 'U';

W poniższej tabeli przedstawiono wyniki tego zapytania dotyczącego nowej instalacji przykładu:

Nazwa tabeli memory_allocated_for_table_kb memory_allocated_for_indexes_kb
SpecialOfferProduct_inmem 64 3840
DemoSalesOrderHeaderSeed 1984 5504
SalesOrderDetail_inmem 15316 663552
DemoSalesOrderDetailSeed 64 10432
SpecialOffer_inmem 3 8192
SalesOrderHeader_inmem 7168 147456
Product_inmem 124 12352

Jak widać, tabele są dość małe: SalesOrderHeader_inmem wynosi około 7 MB i SalesOrderDetail_inmem wynosi około 15 MB rozmiaru.

Co uderza, to rozmiar pamięci przydzielonej do indeksów w porównaniu z rozmiarem danych tabeli. Wynika to z faktu, że indeksy skrótów w przykładzie są dostosowane dla większego rozmiaru danych. Indeksy haszujące mają stały rozmiar, a więc ich rozmiar nie rośnie wraz z rozmiarem danych w tabeli.

Wykorzystanie pamięci po uruchomieniu obciążenia

Po wstawieniu 10 milionów zamówień sprzedaży wykorzystanie całej pamięci wygląda podobnie do następującego zapytania:

SELECT type,
       name,
       pages_kb / 1024 AS pages_MB
FROM sys.dm_os_memory_clerks
WHERE type LIKE '%xtp%';

Oto zestaw wyników.

type name pages_MB
MEMORYCLERK_XTP Domyślny 146
MEMORYCLERK_XTP DB_ID_5 7374
MEMORYCLERK_XTP Domyślny 0
MEMORYCLERK_XTP Domyślny 0

Jak widać, program SQL Server używa nieco poniżej 8 GB dla tabel i indeksów zoptymalizowanych pod kątem pamięci w przykładowej bazie danych.

Przyjrzyj się szczegółowemu użyciu pamięci na tabelę po jednym przykładowym uruchomieniu:

SELECT object_name(t.object_id) AS [Table name],
       memory_allocated_for_table_kb,
       memory_allocated_for_indexes_kb
FROM sys.dm_db_xtp_table_memory_stats AS dms
     INNER JOIN sys.tables AS t
         ON dms.object_id = t.object_id
WHERE t.type = 'U';

Oto zestaw wyników.

Table name memory_allocated_for_table_kb memory_allocated_for_indexes_kb
SalesOrderDetail_inmem 5113761 663552
ZamówienieDemoDetaleSeed 64 10368
SpecialOffer_inmem 2 8192
SalesOrderHeader_inmem 1575679 147456
Product_inmem 111 12032
SpecialOfferProduct_inmem 64 3712
DemoSalesOrderHeaderSeed 1984 5504

Widzimy łącznie około 6,5 GB danych. Rozmiar indeksów w tabeli SalesOrderHeader_inmem i SalesOrderDetail_inmem jest taki sam jak rozmiar indeksów przed wstawieniem zamówień sprzedaży. Rozmiar indeksu nie zmienił się, ponieważ obie tabele używają indeksów skrótów, a indeksy skrótów są statyczne.

Po zresetowaniu pokazu

Procedura składowana Demo.usp_DemoReset może służyć do resetowania demonstracji. Usuwa dane w tabelach SalesOrderHeader_inmem i SalesOrderDetail_inmem, a następnie ponownie zapisuje dane z oryginalnych tabel SalesOrderHeader i SalesOrderDetail.

Teraz, mimo że wiersze w tabelach zostały usunięte, nie oznacza to natychmiastowego odzyskania pamięci. Program SQL Server odzyskuje pamięć z usuniętych wierszy w tabelach zoptymalizowanych pod kątem pamięci w tle zgodnie z potrzebami. Zobaczysz, że zaraz po ponownym uruchomieniu demonstracji, bez transakcyjnego obciążenia w systemie, pamięć z usuniętych wierszy nie została jeszcze odzyskana.

SELECT type,
       name,
       pages_kb / 1024 AS pages_MB
FROM sys.dm_os_memory_clerks
WHERE type LIKE '%xtp%';

Oto zestaw wyników.

type name pages_MB
MEMORYCLERK_XTP Domyślny 2261
MEMORYCLERK_XTP DB_ID_5 7396
MEMORYCLERK_XTP Domyślny 0
MEMORYCLERK_XTP Domyślny 0

Jest to oczekiwane: pamięć jest odzyskiwana, gdy uruchomione jest obciążenie transakcyjne.

Jeśli uruchomisz drugi przebieg obciążenia demonstracyjnego, początkowo zostanie zmniejszone użycie pamięci, ponieważ wcześniej usunięte wiersze zostaną wyczyszczone. W pewnym momencie rozmiar pamięci zwiększa się ponownie do momentu zakończenia obciążenia. Po wstawieniu 10 milionów wierszy po ponownym zresetowaniu bazy danych wykorzystanie pamięci jest bardzo podobne do tego po pierwszym uruchomieniu. Na przykład:

SELECT type,
       name,
       pages_kb / 1024 AS pages_MB
FROM sys.dm_os_memory_clerks
WHERE type LIKE '%xtp%';

Oto zestaw wyników.

type name pages_MB
MEMORYCLERK_XTP Domyślny 1863
MEMORYCLERK_XTP DB_ID_5 7390
MEMORYCLERK_XTP Domyślny 0
MEMORYCLERK_XTP Domyślny 0

Wykorzystanie dysku dla tabel zoptymalizowanych pod kątem pamięci

Ogólny rozmiar dysku dla plików punktu kontrolnego bazy danych w danym momencie można znaleźć przy użyciu zapytania:

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]
FROM sys.filegroups AS f
     INNER JOIN sys.database_files AS df
         ON f.data_space_id = df.data_space_id
WHERE f.type = N'FX';

Stan początkowy

Podczas początkowego tworzenia przykładowej grupy plików i przykładowych tabel zoptymalizowanych pod kątem pamięci kilka plików punktów kontrolnych jest wstępnie tworzonych, a system rozpoczyna wypełnianie plików — liczba wstępnie utworzonych plików punktu kontrolnego zależy od liczby procesorów logicznych w systemie. Ponieważ próbka jest początkowo bardzo mała, wstępnie utworzone pliki są przeważnie puste po początkowym utworzeniu.

Poniższy kod przedstawia początkowy rozmiar dysku dla próbki na maszynie z 16 procesorami logicznymi:

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]
FROM sys.filegroups AS f
     INNER JOIN sys.database_files AS df
         ON f.data_space_id = df.data_space_id
WHERE f.type = N'FX';

Oto zestaw wyników.

Rozmiar dysku w MB
2312

Jak widać, istnieje duża rozbieżność między rozmiarem dysku plików punktu kontrolnego, który wynosi 2,3 GB, a rzeczywistym rozmiarem danych, który jest bliżej 30 MB.

Aby dokładniej przyjrzeć się, skąd pochodzi wykorzystanie miejsca na dysku, możesz użyć następującego zapytania. Rozmiar dysku zwróconego przez to zapytanie jest przybliżony dla plików ze stanem 5 (WYMAGANE DO TWORZENIA KOPII ZAPASOWEJ/HA), 6 (W PRZEJŚCIU DO TOMBSTONE) lub 7 (TOMBSTONE).

SELECT state_desc,
       file_type_desc,
       COUNT(*) AS [count],
       SUM(CASE WHEN state = 5 AND file_type = 0 THEN 128 * 1024 * 1024
                WHEN state = 5 AND file_type = 1 THEN 8 * 1024 * 1024
                WHEN state IN (6, 7) THEN 68 * 1024 * 1024
           ELSE file_size_in_bytes END) / 1024 / 1024 AS [on-disk size MB]
FROM sys.dm_db_xtp_checkpoint_files
GROUP BY state, state_desc, file_type, file_type_desc
ORDER BY state, file_type;

W przypadku początkowego stanu przykładu wynik wygląda podobnie do poniższej tabeli dla serwera z 16 procesorami logicznymi:

opis_stanu opis_typu_pliku liczba / liczyć (context-dependent) rozmiar na dysku w MB
WCZEŚNIEJ UTWORZONE DANE 16 2048
WCZEŚNIEJ UTWORZONE DELTA 16 128
W BUDOWIE DANE 1 128
W BUDOWIE DELTA 1 8

Jak widać, większość miejsca jest używana przez wstępnie utworzone dane i pliki różnicowe. Program SQL Server wstępnie utworzył jedną parę plików (danych, różnicowych) na procesor logiczny. Ponadto pliki danych są wstępnie ustawione na rozmiar 128 MB, a pliki delta na 8 MB, aby zwiększyć wydajność wstawiania danych do tych plików.

Rzeczywiste dane w tabelach zoptymalizowanych pod kątem pamięci są w jednym pliku danych.

Po uruchomieniu zadania

Po uruchomieniu jednego testu, który wstawia 10 milionów zamówień sprzedaży, ogólny rozmiar dysku wygląda mniej więcej tak (dla 16-rdzeniowego serwera testowego):

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]
FROM sys.filegroups AS f
     INNER JOIN sys.database_files AS df
         ON f.data_space_id = df.data_space_id
WHERE f.type = N'FX';

Oto zestaw wyników.

Rozmiar dysku w MB
8828

Rozmiar na dysku wynosi około 9 GB, co jest zbliżone do rozmiaru danych w pamięci.

Dokładniej przyjrzyj się rozmiarom plików punktu kontrolnego w różnych stanach:

SELECT state_desc,
       file_type_desc,
       COUNT(*) AS [count],
       SUM(CASE WHEN state = 5 AND file_type = 0 THEN 128 * 1024 * 1024
                WHEN state = 5 AND file_type = 1 THEN 8 * 1024 * 1024
                WHEN state IN (6, 7) THEN 68 * 1024 * 1024
            ELSE file_size_in_bytes END) / 1024 / 1024 AS [on-disk size MB]
FROM sys.dm_db_xtp_checkpoint_files
GROUP BY state, state_desc, file_type, file_type_desc
ORDER BY state, file_type;

Oto zestaw wyników.

state_desc file_type_desc count on-disk size MB
WCZEŚNIEJ UTWORZONE DANE 16 2048
WCZEŚNIEJ UTWORZONE DELTA 16 128
W BUDOWIE DANE 1 128
W BUDOWIE DELTA 1 8

Nadal mamy 16 par wstępnie utworzonych plików, gotowych do użycia, gdy punkty kontrolne zostaną zamknięte.

Istnieje jedna para w trakcie budowy, która jest używana do momentu zamknięcia bieżącego punktu kontrolnego. Wraz z aktywnymi plikami punktu kontrolnego daje to około 6,5 GB wykorzystania dysku dla 6,5 GB danych w pamięci. Pamiętaj, że indeksy nie są utrwalane na dysku, a więc ogólny rozmiar dysku jest mniejszy niż rozmiar pamięci w tym przypadku.

Po zresetowaniu pokazu

Po zresetowaniu demonstracji miejsce na dysku nie jest natychmiast odzyskiwane, jeśli w systemie nie ma obciążenia transakcyjnego i nie ma punktów kontrolnych bazy danych. Aby pliki punktu kontrolnego mogły być przenoszone przez różne etapy i ostatecznie zostały odrzucone, należy wykonać kilka punktów kontrolnych oraz zdarzenia związane z obcinaniem dziennika, aby zainicjować scalanie plików punktu kontrolnego, a także zainicjować czyszczenie pamięci. Są one wykonywane automatycznie, jeśli masz obciążenie transakcyjne w systemie (i wykonujesz regularne kopie zapasowe dzienników, w przypadku korzystania z modelu PEŁNEGO odzyskiwania), ale nie wtedy, gdy system jest bezczynny, jak w scenariuszu demonstracyjnym.

W tym przykładzie, po zresetowaniu demo, możesz zobaczyć komunikat, jak ten:

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]
FROM sys.filegroups AS f
     INNER JOIN sys.database_files AS df
         ON f.data_space_id = df.data_space_id
WHERE f.type = N'FX';

Oto zestaw wyników.

Rozmiar dysku w MB
11839

Przy wielkości prawie 12 GB, jest to znacznie więcej niż 9 GB, które mieliśmy przed zresetowaniem dema. Jest to spowodowane tym, że rozpoczęto scalanie niektórych plików punktu kontrolnego, ale niektóre obiekty docelowe scalania nie zostały jeszcze zainstalowane, a niektóre pliki źródłowe scalania nie zostały jeszcze wyczyszczone, jak widać w poniższym przykładzie:

SELECT state_desc,
       file_type_desc,
       COUNT(*) AS [count],
       SUM(CASE WHEN state = 5 AND file_type = 0 THEN 128 * 1024 * 1024
                WHEN state = 5 AND file_type = 1 THEN 8 * 1024 * 1024
                WHEN state IN (6, 7) THEN 68 * 1024 * 1024
           ELSE file_size_in_bytes END) / 1024 / 1024 AS [on-disk size MB]
FROM sys.dm_db_xtp_checkpoint_files
GROUP BY state, state_desc, file_type, file_type_desc
ORDER BY state, file_type;

Oto zestaw wyników.

state_desc file_type_desc count on-disk size MB
WCZEŚNIEJ UTWORZONE DANE 16 2048
WCZEŚNIEJ UTWORZONE DELTA 16 128
AKTYWNY DANE 38 5152
AKTYWNY DELTA 38 1331
CEL SCALANIA DANE 7 896
CEL SCALANIA DELTA 7 56
ŹRÓDŁO SCALONE DANE 13 1772
ŹRÓDŁO SCALONE DELTA 13 455

Obiekty docelowe scalania są instalowane i scalane źródło są czyszczone, ponieważ w systemie występuje aktywność transakcyjna.

Po ponownym uruchomieniu demonstracyjnego obciążenia i wstawieniu 10 milionów zamówień sprzedaży po zresetowaniu demonstracji, można zauważyć, że pliki utworzone podczas pierwszego uruchomienia obciążenia zostały wyczyszczone. Jeśli uruchomisz poprzednie zapytanie kilka razy, gdy obciążenie jest uruchomione, zobaczysz, że pliki punktu kontrolnego przechodzą przez różne etapy.

Po drugim uruchomieniu obciążenia, gdy wstawisz 10 milionów zamówień sprzedaży, zobaczysz użycie dysku bardzo podobne do, choć niekoniecznie takie samo jak po pierwszym uruchomieniu, ponieważ system jest dynamiczny. Na przykład:

SELECT state_desc,
       file_type_desc,
       COUNT(*) AS [count],
       SUM(CASE WHEN state = 5 AND file_type = 0 THEN 128 * 1024 * 1024
                WHEN state = 5 AND file_type = 1 THEN 8 * 1024 * 1024
                WHEN state IN (6, 7) THEN 68 * 1024 * 1024
           ELSE file_size_in_bytes END) / 1024 / 1024 AS [on-disk size MB]
FROM sys.dm_db_xtp_checkpoint_files
GROUP BY state, state_desc, file_type, file_type_desc
ORDER BY state, file_type;

Oto zestaw wyników.

state_desc file_type_desc count on-disk size MB
WCZEŚNIEJ UTWORZONE DANE 16 2048
WCZEŚNIEJ UTWORZONE DELTA 16 128
W BUDOWIE DANE 2 268
W BUDOWIE DELTA 2 16
AKTYWNY DANE 41 5608
AKTYWNY DELTA 41 328

W tym przypadku istnieją dwie pary plików punktu kontrolnego w stanie UNDER CONSTRUCTION, co oznacza, że wiele par plików zostało przeniesionych do stanu UNDER CONSTRUCTION, prawdopodobnie z powodu wysokiego poziomu równoczesności obciążenia. Wiele współbieżnych wątków wymaga jednocześnie nowej pary plików, a tym samym przeniesiono parę z PRECREATED do UNDER CONSTRUCTION.