Dela via


Exempeldatabas för In-Memory OLTP

gäller för:SQL ServerAzure SQL Database

Överblick

Det här exemplet visar funktionen In-Memory OLTP. Den visar minnesoptimerade tabeller och inbyggda kompilerade lagrade procedurer och kan användas för att demonstrera prestandafördelar med In-Memory OLTP.

Notera

För att visa det här avsnittet för SQL Server 2014 (12.x), se Tillägg till AdventureWorks för att demonstrera In-Memory OLTP.

Exemplet migrerar fem tabeller i AdventureWorks2022-databasen till minnesoptimerad och innehåller en demoarbetsbelastning för bearbetning av försäljningsorder. Du kan använda den här demoarbetsbelastningen för att se prestandafördelarna med att använda In-Memory OLTP på servern.

I beskrivningen av exemplet diskuterar vi de kompromisser som gjordes när tabellerna migrerades till In-Memory OLTP för att ta hänsyn till de funktioner som inte (ännu) stöds för minnesoptimerade tabeller.

Dokumentationen för det här exemplet är strukturerad på följande sätt:

Förutsättningar

  • SQL Server 2016 (13.x)

  • För prestandatestning, en server med specifikationer som liknar din produktionsmiljö. För det här exemplet bör du ha minst 16 GB minne tillgängligt för SQL Server. Allmänna riktlinjer för maskinvara för In-Memory OLTP finns i följande blogginlägg: Maskinvaruöverväganden för In-Memory OLTP i SQL Server 2014

Installera In-Memory OLTP-exempel baserat på AdventureWorks

Följ de här stegen för att installera exemplet:

  1. Ladda ned AdventureWorks2016_EXT.bak och SQLServer2016Samples.zip från: https://github.com/microsoft/sql-server-samples/releases/tag/adventureworks till en lokal mapp, till exempel C:\Temp.

  2. Återställ databassäkerhetskopian med hjälp av Transact-SQL eller SQL Server Management Studio:

    1. Identifiera målmappen och filnamnet för datafilen, till exempel

      'h:\DATA\AdventureWorks2022_Data.mdf'

    2. Identifiera målmappen och filnamnet för loggfilen, till exempel

      'i:\DATA\AdventureWorks2022_log.ldf'

      1. Loggfilen ska placeras på en annan enhet än datafilen, helst en enhet med låg svarstid, till exempel en SSD- eller PCIe-lagring, för maximal prestanda.

    Exempel på T-SQL-skript:

    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. Om du vill visa exempelskript och arbetsbelastningar packar du upp filen SQLServer2016Samples.zip till en lokal mapp. Mer information om hur du kör arbetsbelastningen finns i filen In-Memory OLTP-\readme.txt.

Beskrivning av exempeltabeller och procedurer

Exemplet skapar nya tabeller för produkter och försäljningsorder baserat på befintliga tabeller i AdventureWorks2022. Schemat för de nya tabellerna liknar de befintliga tabellerna, med några skillnader, enligt beskrivningen nedan.

De nya minnesoptimerade tabellerna har suffixet "_inmem". Exemplet innehåller även motsvarande tabeller med suffixet "_ondisk" – dessa tabeller kan användas för att göra en en-till-en-jämförelse mellan prestanda för minnesoptimerade tabeller och diskbaserade tabeller i systemet.

De minnesoptimerade tabeller som används i arbetsbelastningen för prestandajämförelse är helt hållbara och fullständigt loggade. De offrar inte hållbarhet eller tillförlitlighet för att uppnå prestandavinsten.

Målarbetsbelastningen för det här exemplet är bearbetning av försäljningsorder, där vi även överväger information om produkter och rabatter. Därför använder vi tabellerna SalesOrderHeader, SalesOrderDetail, Product, SpecialOfferoch SpecialOfferProduct.

Två nya lagrade procedurer, Sales.usp_InsertSalesOrder_inmem och Sales.usp_UpdateSalesOrderShipInfo_inmem, används för att infoga försäljningsorder och uppdatera leveransinformationen för en viss försäljningsorder.

Det nya schemat Demo innehåller hjälptabeller och lagrade procedurer för att köra en demoarbetsbelastning.

Konkret lägger In-Memory OLTP-exemplet till följande objekt i AdventureWorks2022:

Tabeller som lagts till av exemplet

De nya tabellerna

Sales.SalesOrderHeader_inmem

  • Rubrikinformation om försäljningsorder. Varje försäljningsorder har en rad i den här tabellen.

Sales.SalesOrderDetail_inmem

  • Information om försäljningsorder. Varje radobjekt i en försäljningsorder har en rad i den här tabellen.

Sales.SpecialOffer_inmem

  • Information om specialerbjudanden, inklusive rabattprocenten som är associerad med varje specialerbjudande.

Sales.SpecialOfferProduct_inmem

  • Referenstabell mellan specialerbjudanden och produkter. Varje specialerbjudande kan innehålla noll eller fler produkter, och varje produkt kan presenteras i noll eller fler specialerbjudanden.

Production.Product_inmem

  • Information om produkter, inklusive deras listpris.

Demo.DemoSalesOrderDetailSeed

  • Används i demobelastningen för att skapa exempelförsäljningsorder.

Diskbaserade varianter av tabellerna:

  • Sales.SalesOrderHeader_ondisk

  • Sales.SalesOrderDetail_ondisk

  • Sales.SpecialOffer_ondisk

  • Sales.SpecialOfferProduct_ondisk

  • Production.Product_ondisk

Skillnader mellan ursprungliga diskbaserade och nya minnesoptimerade tabeller

För det mesta använder de nya tabeller som introducerades i det här exemplet samma kolumner och samma datatyper som de ursprungliga tabellerna. Det finns dock några skillnader. Vi listar skillnaderna nedan, tillsammans med en motivering för ändringarna.

Sales.SalesOrderHeader_inmem

  • Standardbegränsningar stöds för minnesoptimerade tabeller, och de flesta standardbegränsningar migrerade vi som de är. Den ursprungliga tabellen Sales.SalesOrderHeader innehåller dock två standardbegränsningar som hämtar det aktuella datumet, för kolumnerna OrderDate och ModifiedDate. I en orderbearbetningsarbetsbelastning med högt dataflöde med mycket samtidighet kan alla globala resurser bli en konkurrensfråga. Systemtiden är en sådan global resurs och vi har observerat att det kan bli en flaskhals när du kör en In-Memory OLTP-arbetsbelastning som infogar försäljningsorder, särskilt om systemtiden behöver hämtas för flera kolumner i försäljningsorderhuvudet samt försäljningsorderinformationen. Problemet åtgärdas i det här exemplet genom att bara hämta systemtiden en gång för varje försäljningsorder som infogas och använda det värdet för datetime-kolumnerna i SalesOrderHeader_inmem och SalesOrderDetail_inmemi den lagrade proceduren Sales.usp_InsertSalesOrder_inmem.

  • Användardefinierade datatyper för alias (UDT) – Den ursprungliga tabellen använder två alias-UDT:er dbo.OrderNumber och dbo.AccountNumberför kolumnerna PurchaseOrderNumber respektive AccountNumber. SQL Server 2016 (13.x) stöder inte alias-UDT för minnesoptimerade tabeller. Därför använder de nya tabellerna systemdatatyperna nvarchar(25) respektive nvarchar(15).

  • nullbara kolumner i indexnycklar – I den ursprungliga tabellen kan kolumnen SalesPersonID vara null, medan kolumnen i de nya tabellerna inte är nullbar och har en standardbegränsning med värdet (-1). Den här situationen beror på att index på minnesoptimerade tabeller inte kan ha nullbara kolumner i indexnyckeln. -1 är surrogat för NULL i det här fallet.

  • Beräknade kolumner – De beräknade kolumnerna SalesOrderNumber och TotalDue utelämnas eftersom SQL Server 2016 (13.x) inte stöder beräknade kolumner i minnesoptimerade tabeller. Den nya vyn Sales.vSalesOrderHeader_extended_inmem återspeglar kolumnerna SalesOrderNumber och TotalDue. Därför kan du använda den här vyn om dessa kolumner behövs.

    • gäller för: SQL Server 2017 (14.x) CTP 1.1.
      Från och med SQL Server 2017 (14.x) CTP 1.1 stöds beräknade kolumner i minnesoptimerade tabeller och index.
  • Främmande nyckelbegränsningar stöds för minnesoptimerade tabeller i SQL Server 2016 (13.x), men endast om de refererade tabellerna också är minnesoptimerade. Sekundärnycklar som refererar till tabeller som också migreras till minnesoptimerade sparas i de migrerade tabellerna, medan andra sekundärnycklar utelämnas. Dessutom är SalesOrderHeader_inmem en het tabell i exempelarbetsbelastningen, och begränsningar för främmande nycklar kräver ytterligare bearbetning för alla DML-åtgärder, då det kräver uppslagningar i alla andra tabeller som refereras till i dessa begränsningar. Därför är antagandet att appen säkerställer referensintegritet för Sales.SalesOrderHeader_inmem-tabellen, och referensintegriteten verifieras inte när rader infogas.

  • Rowguid – Kolumnen rowguid utelämnas. Även om uniqueidentifier stöds för minnesoptimerade tabeller stöds inte alternativet ROWGUIDCOL i SQL Server 2016 (13.x). Kolumner av den här typen används vanligtvis för antingen sammanslagningsreplikering eller tabeller som har filströmskolumner. Det här exemplet innehåller ingetdera.

Försäljning.Försäljningsorderdetalj

  • Standardbegränsningar , som liknar SalesOrderHeader, migreras inte standardvillkoret som kräver systemdatum/tid, utan den lagrade proceduren som infogar försäljningsorder tar hand om att infoga det aktuella systemets datum/tid vid första infogningen.

  • beräknade kolumner – den beräknade kolumnen LineTotal migrerades inte eftersom minnesoptimerade tabeller inte stöder beräknade kolumner i SQL Server 2016 (13.x). Om du vill komma åt den här kolumnen använder du vyn Sales.vSalesOrderDetail_extended_inmem.

  • Rowguid – kolumnen rowguid utelämnas. Mer information finns i beskrivningen för tabellen SalesOrderHeader.

Produktion.Produkt

  • alias-UDT:er – den ursprungliga tabellen använder den användardefinierade datatypen dbo.Flag, vilket motsvarar biten för systemdatatypen. Den migrerade tabellen använder bitdatatypen i stället.

  • Rowguid – kolumnen rowguid utelämnas. Mer information finns i beskrivningen för tabellen SalesOrderHeader.

Försäljning.Specialerbjudande

  • Rowguid – kolumnen rowguid utelämnas. Mer information finns i beskrivningen för tabellen SalesOrderHeader.

Försäljning.SPECIALERBJUDANDEPRODUKT

  • Rowguid – kolumnen rowguid utelämnas. Mer information finns i beskrivningen för tabellen SalesOrderHeader.

Överväganden för index i minnesoptimerade tabeller

Baslinjeindexet för minnesoptimerade tabeller är NONCLUSTERED-indexet, som stöder punktsökningar (indexsökning på likhetspredikat), intervallgenomsökningar (index söker efter ojämlikhetspredikat), fullständiga indexgenomsökningar och ordnade genomsökningar. Dessutom stöder NONCLUSTERED-index sökning på inledande kolumner i indexnyckeln. Minnesoptimerade NONCLUSTERED-index stöder faktiskt alla åtgärder som stöds av diskbaserade NONCLUSTERED-index, med det enda undantaget är bakåtgenomsökningar. Därför är det säkert att använda NONCLUSTERED-index för dina index.

HASH-index kan användas för att ytterligare optimera arbetsbelastningen. De är optimerade för punktuppslag och radinsättningar. Man måste dock tänka på att de inte stöder intervallgenomsökningar, ordnade genomsökningar eller sökning på ledande indexnyckelkolumner. Därför måste du vara försiktig när du använder dessa index. Dessutom är det nödvändigt att ange bucket_count vid skapandetillfället. Det bör vanligtvis anges till mellan en och två gånger så många indexnyckelvärden, men överskattning är vanligtvis inte ett problem.

Mer information finns i:

Indexen för de migrerade tabellerna har finjusterats för hanteringen av försäljningsorder i demouppdrag. Arbetsbelastningen förlitar sig på infogningar och punktsökningar i tabellerna Sales.SalesOrderHeader_inmem och Sales.SalesOrderDetail_inmemoch förlitar sig även på punktsökningar på primärnyckelkolumnerna i tabellerna Production.Product_inmem och Sales.SpecialOffer_inmem.

Sales.SalesOrderHeader_inmem har tre index, som alla är HASH-index av prestandaskäl, och eftersom inga sorteringar eller intervallgenomsökningar behövs för arbetsbelastningen.

  • HASH-index på (SalesOrderID): bucket_count har en storlek på 10 miljoner (avrundat upp till 16 miljoner), eftersom det förväntade antalet försäljningsorder är 10 miljoner

  • HASH-index på (SalesPersonID): bucket_count är 1 miljon. Det uppgivna datasetet har inte många säljare. Men denna stora bucket_count möjliggör framtida tillväxt. Dessutom betalar du inte en prestandaavgift för poängsökningar om bucket_count är överdimensionerad.

  • HASH-index på (CustomerID): bucket_count är 1 miljon. Den angivna datamängden har inte många kunder, men detta möjliggör framtida tillväxt.

Sales.SalesOrderDetail_inmem har tre index, som alla är HASH-index av prestandaskäl, och eftersom inga sorteringar eller intervallgenomsökningar behövs för arbetsbelastningen.

  • HASH-index på (SalesOrderID, SalesOrderDetailID): det här är det primära nyckelindexet, och även om sökningar på (SalesOrderID, SalesOrderDetailID) är sällsynta, använder man ett hash-index för nyckeln för att påskynda radinfogningar. Bucket_count är satt till 50 miljoner (avrundat upp till 67 miljoner); det förväntade antalet försäljningsorder är 10 miljoner, och denna är dimensionerad för att ha ett genomsnitt på fem artiklar per order.

  • HASH-index på (SalesOrderID): sökningar efter försäljningsorder är vanliga: du vill hitta alla radobjekt som motsvarar en enskild order. bucket_count är storleksanpassat till 10 miljoner (avrundat upp till 16 miljoner), eftersom det förväntade antalet försäljningsorder är 10 miljoner

  • HASH-index på (ProductID): bucket_count är 1 miljon. Den angivna datamängden har inte mycket produkt, men detta möjliggör framtida tillväxt.

Production.Product_inmem har tre index

  • HASH-index på (ProductID): sökningar på ProductID ligger i den kritiska vägen för demobelastningen, därför är detta ett hash-index.

  • NONCLUSTERED-index på (Name): det här tillåter ordnade genomsökningar av produktnamn.

  • NONCLUSTERED-index på (ProductNumber): detta tillåter ordnade genomsökningar av produktnummer

Sales.SpecialOffer_inmem har ett HASH-index på (SpecialOfferID): punktsökningar av specialerbjudanden finns i den kritiska delen av demoarbetsbelastningen. bucket_count har en storlek på 1 miljon för att möjliggöra framtida tillväxt.

Sales.SpecialOfferProduct_inmem refereras inte i demoarbetsbelastningen och därför finns det inget uppenbart behov av att använda hashindex i den här tabellen för att optimera arbetsbelastningen – indexen på (SpecialOfferID, ProductID) och (ProductID) är NONCLUSTERED.

Observera att i ovanstående är vissa bucketantal överdimensionerade, men inte bucketantalet för indexen på SalesOrderHeader_inmem och SalesOrderDetail_inmem: de är storleksanpassade för bara 10 miljoner försäljningsorder. Detta gjordes för att tillåta installation av exemplet på system med låg minnestillgänglighet, men i dessa fall misslyckas demoarbetsbelastningen med slut på minne. Om du vill skala långt över 10 miljoner försäljningsorder kan du öka antalet bucketar i enlighet med detta.

Överväganden för minnesanvändning

Minnesanvändning i exempeldatabasen, både före och efter körning av demoarbetsbelastningen, beskrivs i avsnittet Minnesanvändning för de minnesoptimerade tabellerna.

Lagrade procedurer som lagts till av exemplet

De två viktiga lagrade procedurerna för att infoga försäljningsorder och uppdatera leveransinformationen är följande:

  • Sales.usp_InsertSalesOrder_inmem

    • Infogar en ny försäljningsorder i databasen och matar ut SalesOrderID för försäljningsordern. Som indataparametrar tar den information om försäljningsorderns huvud samt raderna i ordern.

    • Utdataparameter:

      • @SalesOrderID int – SalesOrderID för försäljningsordern som just infogades
    • Indataparametrar (krävs):

      • @DueDate datetime2

      • @CustomerID int

      • @BillToAddressID [int]

      • @ShipToAddressID [int]

      • @ShipMethodID [int]

      • @SalesOrderDetails Sales.SalesOrderDetailType_inmem – tabellvärdesparameter (TVP) som innehåller radobjekten i ordern

    • Indataparametrar (valfritt):

      • @Status [tinyint]

      • @OnlineOrderFlag [bit]

      • @PurchaseOrderNumber [nvarchar](25)

      • @AccountNumber [nvarchar](15)

      • @SalesPersonID [int]

      • @TerritoryID [int]

      • @CreditCardID [int]

      • @CreditCardApprovalCode [varchar](15)

      • @CurrencyRateID [int]

      • @Comment nvarchar(128)

  • Sales.usp_UpdateSalesOrderShipInfo_inmem

    • Uppdatera leveransinformationen för en viss försäljningsorder. Detta uppdaterar även leveransinformationen för alla rader i beställningen.

    • Det här är en omslutningsprocedur för inbyggda kompilerade lagrade procedurer Sales.usp_UpdateSalesOrderShipInfo_native med logik för återförsök för att hantera (oväntade) potentiella konflikter med samtidiga transaktioner som uppdaterar samma ordning. Mer information finns i omförsökslogik.

  • Sales.usp_UpdateSalesOrderShipInfo_native

    • Det här är den internt kompilerade lagrade proceduren som faktiskt bearbetar uppdateringen av leveransinformationen. Det är avsett att anropas från den lagrade omslutningsproceduren Sales.usp_UpdateSalesOrderShipInfo_inmem. Om klienten kan hantera fel och implementerar logik för återförsök kan du anropa den här proceduren direkt i stället för att använda den omslutna lagrade proceduren.

Följande lagrade procedur används för demoarbetsbelastningen.

  • Demo.usp_DemoReset

    • Återställer demon genom att tömma och påfylla tabellerna SalesOrderHeader och SalesOrderDetail.

Följande lagrade procedurer används för att infoga och ta bort från minnesoptimerade tabeller samtidigt som domän och referensintegritet garanteras.

  • Production.usp_InsertProduct_inmem

  • Production.usp_DeleteProduct_inmem

  • Sales.usp_InsertSpecialOffer_inmem

  • Sales.usp_DeleteSpecialOffer_inmem

  • Sales.usp_InsertSpecialOfferProduct_inmem

Slutligen används följande lagrade procedur för att verifiera domän och referensintegritet.

  1. dbo.usp_ValidateIntegrity

    • Valfri parameter: @object_id – ID för objektet som ska verifiera integriteten för

    • Den här proceduren förlitar sig på tabellerna dbo.DomainIntegrity, dbo.ReferentialIntegrityoch dbo.UniqueIntegrity för de integritetsregler som måste verifieras . Exemplet fyller i dessa tabeller baserat på de kontroll-, sekundärnyckel- och unika begränsningar som finns för de ursprungliga tabellerna i AdventureWorks2022-databasen.

    • Den förlitar sig på hjälpprocedurerna dbo.usp_GenerateCKCheck, dbo.usp_GenerateFKCheckoch dbo.GenerateUQCheck för att generera den T-SQL som behövs för att utföra integritetskontrollerna.

Prestandamätningar med demoarbetsbelastningen

Ostress är ett kommandoradsverktyg som har utvecklats av microsoft CSS SQL Server-supportteamet. Det här verktyget kan användas för att köra frågor eller köra lagrade procedurer parallellt. Du kan konfigurera antalet trådar för att köra en viss T-SQL-instruktion parallellt, och du kan ange hur många gånger instruktionen ska köras på den här tråden. ostress startar trådarna och kör -instruktionen på alla trådar parallellt. När körningen är klar för alla trådar kommer ostress att rapportera den tid det tog för alla att slutföra körningen.

Installera ostress

Ostress installeras som en del av RML-verktyg (Report Markup Language). det finns ingen fristående installation för ostress.

Installationssteg:

  1. Ladda ned och kör x64-installationspaketet för RML-verktygen från följande sida: Ladda ned RML för SQL Server

  2. Om det finns en dialogruta som anger att vissa filer används väljer du Fortsätt

Kör ostress

Ostress körs från kommandotolken. Det är mest praktiskt att köra verktyget från "RML Cmd Prompt", som installeras som en del av RML-verktygen.

Om du vill öppna RML-cmd prompten följer du dessa instruktioner:

Öppna Start-menyn i Windows genom att välja Windows-nyckeln och skriv rml. Välj "RML Cmd Prompt", som visas i listan med sökresultat.

Kontrollera att kommandotolken finns i installationsmappen för RML-verktyg.

Kommandoradsalternativen för ostress visas när du bara kör ostress.exe utan kommandoradsalternativ. De viktigaste alternativen för att köra ostress med det här exemplet är:

  • -S-namnet på Microsoft SQL Server-instansen som ska anslutas till

  • -E använder Windows-autentisering för att ansluta (standard); Om du använder SQL Server-autentisering använder du alternativen -U och -P för att ange användarnamn respektive lösenord

  • -d namnet på databasen, för det här exemplet AdventureWorks2022

  • -Q T-SQL-instruktionen som ska köras

  • -n antal anslutningar som bearbetar varje indatafil/fråga

  • -är antalet iterationer för varje anslutning för att köra varje indatafil/fråga

Demoarbetsbelastning

Den huvudsakliga lagrade proceduren som används i demoarbetsbelastningen är Sales.usp_InsertSalesOrder_inmem/ondisk. Skriptet i nedanstående konstruerar en tabellvärdesparameter (TVP) med exempeldata och anropar proceduren för att infoga en försäljningsorder med fem radobjekt.

Ostress-verktyget används för att köra de lagrade proceduranropen parallellt för att simulera klienter som infogar försäljningsorder samtidigt.

Återställ demonstrationen efter varje stresskörning som körs Demo.usp_DemoReset. Den här proceduren tar bort raderna i de minnesoptimerade tabellerna, trunkerar de diskbaserade tabellerna och kör en databaskontrollpunkt.

Följande skript körs samtidigt för att simulera en arbetsbelastning för bearbetning av försäljningsorder:

DECLARE   
      @i int = 0,   
      @od Sales.SalesOrderDetailType_inmem,   
      @SalesOrderID int,   
      @DueDate datetime2 = sysdatetime(),   
      @CustomerID int = rand() * 8000,   
      @BillToAddressID int = rand() * 10000,   
      @ShipToAddressID int = rand() * 10000,   
      @ShipMethodID 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;   
      EXEC Sales.usp_InsertSalesOrder_inmem @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od;   
      SET @i += 1   
END

Med det här skriptet infogas varje provtagningsordning som är konstruerad 20 gånger, genom 20 lagrade procedurer som körs i en WHILE-loop. Loopen används för att ta hänsyn till det faktum att databasen används för att konstruera exempelordningen. I typiska produktionsmiljöer konstruerar mellannivåprogrammet den försäljningsorder som ska infogas.

Skriptet ovan infogar försäljningsorder i minnesoptimerade tabeller. Skriptet för att infoga försäljningsorder i diskbaserade tabeller härleds genom att ersätta de två förekomsterna av "_inmem" med "_ondisk".

Vi använder ostress-verktyget för att köra skripten med flera samtidiga anslutningar. Vi använder parametern "-n" för att styra antalet anslutningar och parametern "r" för att styra hur många gånger skriptet körs på varje anslutning.

Köra arbetsbelastningen

För att testa i stor skala infogar vi 10 miljoner försäljningsorder med hjälp av 100 anslutningar. Det här testet fungerar rimligt på en blygsam server (till exempel 8 fysiska, 16 logiska kärnor) och grundläggande SSD-lagring för loggen. Om testet inte fungerar bra på din maskinvara, se över avsnittet Felsökning av långsamt körande tester. Om du vill minska stressnivån för det här testet sänker du antalet anslutningar genom att ändra parametern "-n". Om du till exempel vill sänka antalet anslutningar till 40 ändrar du parametern "-n100" till "-n40".

Som ett prestandamått för arbetsbelastningen använder vi den förflutna tiden som rapporterats av ostress.exe efter att ha kört arbetsbelastningen.

Anvisningarna och mätvärdena nedan använder en belastning som infogar 10 miljoner försäljningsorder. Instruktioner för att köra en nedskalad arbetsbelastning som infogar 1 miljon försäljningsorder finns i anvisningarna i "In-Memory OLTP\readme.txt' that is part of the SQLServer2016Samples.zip arkiv.

Minnesoptimerade tabeller

Vi börjar med att köra arbetsbelastningen på minnesoptimerade tabeller. Följande kommando öppnar 100 trådar, var och en körs för 5 000 iterationer. Varje iteration infogar 20 försäljningsorder i separata transaktioner. Det finns 20 infogningar per iteration för att kompensera för det faktum att databasen används för att generera de data som ska infogas. Detta ger totalt 20 * 5 000 * 100 = 10 000 000 försäljningsorderinfogningar.

Öppna RML-cmd prompten och kör följande kommando:

Välj knappen Kopiera för att kopiera kommandot och klistra in det i kommandotolken för RML Utilities.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i int = 0, @od Sales.SalesOrderDetailType_inmem, @SalesOrderID int, @DueDate datetime2 = sysdatetime(), @CustomerID int = rand() * 8000, @BillToAddressID int = rand() * 10000, @ShipToAddressID int = rand() * 10000, @ShipMethodID 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; EXEC Sales.usp_InsertSalesOrder_inmem @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; set @i += 1 end"  

På en testserver med totalt 8 fysiska (16 logiska) kärnor tog det 2 minuter och 5 sekunder. På en andra testserver med 24 fysiska (48 logiska) kärnor tog det 1 minut och 0 sekunder.

Observera CPU-användningen medan arbetsbelastningen körs, till exempel med hjälp av Aktivitetshanteraren. Du ser att processoranvändningen är nära 100%. Om så inte är fallet, har du en logg-I/O-flaskhals, se även Felsöka långsamma tester.

Diskbaserade tabeller

Följande kommando kör arbetsbelastningen på diskbaserade tabeller. Den här arbetsbelastningen kan ta ett tag att utföra, vilket till stor del beror på lås-konflikt i systemet. Minnesoptimerad tabell är spärrfri och lider därför inte av det här problemet.

Öppna RML-cmd prompten och kör följande kommando:

Välj knappen Kopiera för att kopiera kommandot och klistra in det i kommandotolken RML Utilities.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i int = 0, @od Sales.SalesOrderDetailType_ondisk, @SalesOrderID int, @DueDate datetime2 = sysdatetime(), @CustomerID int = rand() * 8000, @BillToAddressID int = rand() * 10000, @ShipToAddressID int = rand() * 10000, @ShipMethodID 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; EXEC Sales.usp_InsertSalesOrder_ondisk @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; set @i += 1 end"  

På en testserver med totalt 8 fysiska (16 logiska) kärnor tog det 41 minuter och 25 sekunder. På en andra testserver med 24 fysiska (48 logiska) kärnor tog det 52 minuter och 16 sekunder.

Den viktigaste faktorn i prestandaskillnaden mellan minnesoptimerade tabeller och diskbaserade tabeller i det här testet är det faktum att SQL Server inte fullt ut kan använda processorn när du använder diskbaserade tabeller. Orsaken är spärrar: samtidiga transaktioner försöker skriva till samma datasida, och spärrar används för att säkerställa att endast en transaktion i taget kan skriva till en sida. In-Memory OLTP-motorn är spärrfri och datarader ordnas inte på sidor. Samtidiga transaktioner blockerar därför inte varandras infogningar, vilket gör det möjligt för SQL Server att utnyttja processorn fullt ut.

Du kan se processoranvändningen medan arbetsbelastningen körs, till exempel med hjälp av Aktivitetshanteraren. Med diskbaserade tabeller visas processoranvändningen långt ifrån 100%. Vid en testkonfiguration med 16 logiska processorer ligger användningen på cirka 24%.

Du kan, om så önskas, visa antalet spärrväntningar per sekund med hjälp av Performance Monitor, med prestandaräknaren \SQL Server:Latches\Latch Waits/sec.

Återställa demoversionen

Om du vill återställa demonstrationen öppnar du RML Cmd Prompt och kör följande kommando:

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

Beroende på maskinvaran kan det ta några minuter att köra.

Vi rekommenderar en återställning efter varje demokörning. Eftersom den här arbetsbelastningen är infogningsbaserad kommer varje körning att förbruka mer minne, och därför krävs en återställning för att undvika att minnet tar slut. Mängden minne som förbrukas efter en körning beskrivs i avsnittet Minnesanvändning när arbetsbelastningenhar körts.

Felsöka tester som körs långsamt

Testresultaten varierar vanligtvis med maskinvara och även den samtidighetsnivå som används i testkörningen. Ett par saker att leta efter om resultatet inte är som förväntat:

  • Antal samtidiga transaktioner: När du kör arbetsbelastningen på en enda tråd blir prestandavinsten med In-Memory OLTP sannolikt mindre än 2X. Spärrkonkurrens är bara ett betydande problem om det finns en hög grad av samtidighet.

  • Lågt antal kärnor som är tillgängliga för SQL Server: Det innebär att det kommer att finnas en låg samtidighetsnivå i systemet, eftersom det bara kan finnas så många transaktioner som körs samtidigt som det finns kärnor tillgängliga för SQL.

    • Symptom: Om processoranvändningen är hög när du kör arbetsbelastningen på diskbaserade tabeller innebär det att det inte finns mycket konkurrens, vilket pekar på bristande samtidighet.
  • Loggenhetens hastighet: Om loggenheten inte kan hålla jämna steg med transaktionens genomströmning i systemet, blir arbetsbelastningen en flaskhals på logg-I/O. Loggning är effektivare med In-Memory OLTP, men om logg-I/O är en flaskhals är den potentiella prestandavinsten begränsad.

    • Symptom: Om processoranvändningen inte är nära 100% eller är mycket spikig när du kör arbetsbelastningen på minnesoptimerade tabeller är det möjligt att det finns en logg-I/O-flaskhals. Detta kan bekräftas genom att öppna Resursövervakaren och titta på kölängden för loggdisken.

Minnes- och diskutrymmesanvändning i exemplet

I nedanstående beskriver vi vad du kan förvänta dig när det gäller minnes- och diskutrymmesanvändning för exempeldatabasen. Vi visar också de resultat som vi har sett i på en testserver med 16 logiska kärnor.

Minnesanvändning för de minnesoptimerade tabellerna

Övergripande användning av databasen

Följande fråga kan användas för att hämta den totala minnesanvändningen för In-Memory OLTP i systemet.

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

Ögonblicksbild efter att databasen just har skapats:

typ Namn sidor_MB
MEMORYCLERK_XTP Förvalt 94
MEMORYCLERK_XTP DB_ID_5 877
MEMORYCLERK_XTP Förvalt 0
MEMORYCLERK_XTP Standard 0

Standardminneshanterarna innehåller systemomfattande minnesstrukturer och är relativt små. Minneshanteringen för användardatabasen, i det här fallet databasen med ID 5 (database_id kan skilja sig åt i din instans), är cirka 900 MB.

Minnesanvändning per tabell

Följande fråga kan användas för att öka detaljnivån i minnesanvändningen för de enskilda tabellerna och deras index:

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 dms JOIN sys.tables t   
ON dms.object_id=t.object_id  
WHERE t.type='U';  

I följande tabell visas resultatet av den här frågan för en ny installation av exemplet:

Tabellnamn minne_tilldelat_för_tabell_kb minne_tilldelat_för_index_kb
SpecialOfferProduct_inmem 64 3840
DemoFörsäljningsOrderHuvudFrö 1984 5504
SalesOrderDetail_inmem 15316 663552
Demoförsäljningsorderdetaljjämförelse 64 10432
SpecialOffer_inmem 3 8192
SalesOrderHeader_inmem 7168 147456
Product_inmem 124 12352

Som du ser är tabellerna ganska små: SalesOrderHeader_inmem är cirka 7 MB och SalesOrderDetail_inmem är ungefär 15 MB stora.

Det som är slående här är storleken på det minne som allokerats för index, jämfört med storleken på tabelldata. Det beror på att hash-indexen i exemplet är förstorade för en större datastorlek. Observera att hash-index har en fast storlek och att deras storlek därför inte växer med storleken på data i tabellen.

Minnesanvändning när arbetsbelastningen har körts

När du har infogat 10 miljoner försäljningsorder ser all-up-minnesanvändningen ut ungefär så här:

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%'  
typ Namn sidor_MB
MEMORYCLERK_XTP Förvald 146
MEMORYCLERK_XTP DB_ID_5 7374
MEMORYCLERK_XTP Standard 0
MEMORYCLERK_XTP Standard 0

Som du ser använder SQL Server lite under 8 GB för de minnesoptimerade tabellerna och indexen i exempeldatabasen.

Titta på den detaljerade minnesanvändningen per tabell efter en exempelkörning:

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 dms JOIN sys.tables t   
ON dms.object_id=t.object_id  
WHERE t.type='U'  
Tabellnamn minne_tilldelat_för_tabell_kb minne_tilldelat_för_index_kb
SalesOrderDetail_inmem 5113761 663552
DemoSalesOrderDetailSeed 64 10368
SärskildErbjudande_inmem 2 8192
SalesOrderHeader_inmem 1575679 147456
Product_inmem 111 12032
SpecialOfferProduct_inmem 64 3712
DemoSalesOrderHeaderSeed 1984 5504

Vi kan se totalt cirka 6,5 GB data. Observera att storleken på indexen i tabellen SalesOrderHeader_inmem och SalesOrderDetail_inmem är samma som storleken på indexen innan du infogar försäljningsorder. Indexstorleken ändrades inte eftersom båda tabellerna använder hash-index och hash-index är statiska.

Efter demoåterställning

Den lagrade proceduren Demo.usp_DemoReset kan användas för att återställa demonstrationen. Den tar bort data i tabellerna SalesOrderHeader_inmem och SalesOrderDetail_inmemoch återställer data från de ursprungliga tabellerna SalesOrderHeader och SalesOrderDetail.

Även om raderna i tabellerna har tagits bort betyder det inte att minnet frigörs omedelbart. SQL Server återtar minne från borttagna rader i minnesoptimerade tabeller i bakgrunden efter behov. Du kommer att se att omedelbart efter att demon har återställts, utan att det finns någon transaktionsarbetsbelastning på systemet, har minnet från borttagna rader ännu inte återkrävts.

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%';
typ Namn sidor_MB
MEMORYCLERK_XTP Förval 2261
MEMORYCLERK_XTP DB_ID_5 7396
MEMORYCLERK_XTP Standard 0
MEMORYCLERK_XTP Standard 0

Detta förväntas: minnet kommer att frigöras när transaktionslasten körs.

Om du startar en andra körning av demoarbetsbelastningen ser du att minnesanvändningen minskar från början, eftersom de tidigare borttagna raderna rensas. Vid något tillfälle ökar minnesstorleken igen tills arbetsbelastningen är klar. När du har infogat 10 miljoner rader efter demoåterställningen är minnesanvändningen mycket lik användningen efter den första körningen. Till exempel:

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%';
typ Namn pages_MB
MEMORYCLERK_XTP Förvald 1863
MEMORYCLERK_XTP DB_ID_5 7390
MEMORYCLERK_XTP Standard 0
MEMORYCLERK_XTP Standard 0

Diskanvändning för minnesoptimerade tabeller

Den övergripande storleken på disken för kontrollpunktsfilerna för en databas vid en viss tidpunkt finns med hjälp av frågan:

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

Initialt tillstånd

När exempelfilgruppen och minnesoptimerade exempeltabeller skapas från början skapas ett antal kontrollpunktsfiler och systemet börjar fylla filerna – antalet kontrollpunktsfiler som skapats i förväg beror på antalet logiska processorer i systemet. Eftersom exemplet till en början är mycket litet är de förskapade filerna mestadels tomma efter den första skapande.

Följande kod visar den ursprungliga storleken på disken för exemplet på en dator med 16 logiska processorer:

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

Som du ser finns det en stor skillnad mellan disklagringsstorleken på kontrollpunktfilerna, som är 2,3 GB, och den faktiska datastorleken, som är närmare 30 MB.

Om du tittar närmare på var diskutrymmesanvändningen kommer ifrån kan du använda följande fråga. Storleken på disken som returneras av den här frågan är ungefärlig för filer med tillstånd i 5 (KRÄVS FÖR SÄKERHETSKOPIERing/HA), 6 (I ÖVERGÅNG TILL TOMBSTONE) eller 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;

För det inledande tillståndet för exemplet ser resultatet ut ungefär så här för en server med 16 logiska processorer:

tillstånd_beskrivning file_type_desc räkna Storlek på disken MB
FÖRSKAPAD DATA 16 2048
FÖRSKAPAD DELTA 16 128
Under konstruktion DATA 1 128
UNDER KONSTRUKTION DELTA 1 8

Som du ser används det mesta av utrymmet av förskapade data och deltafiler. SQL Server skapade i förväg ett par (data, delta) filer per logisk processor. Dessutom är datafilerna på förhand förstorade till 128 MB och deltafiler till 8 MB för att göra det mer effektivt att infoga data i dessa filer.

Faktiska data i de minnesoptimerade tabellerna finns i den enskilda datafilen.

När du har kört arbetslasten

Efter en enda testkörning som infogar 10 miljoner försäljningsorder ser den totala storleken på disken ut ungefär så här (för en 16-kärns testserver):

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

Storleken på disken är nära 9 GB, vilket är nära minnesintern storlek på data.

En närmare titt på storlekarna på kontrollpunktsfilerna i de olika tillstånden:

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;
statusbeskrivning filtyp_beskrivning räkna Storlek på disken i MB
FÖRUTSKAPAD DATA 16 2048
FÖRSKAPAD DELTA 16 128
BYGGE PÅGÅR DATA 1 128
UNDER KONSTRUKTION DELTA 1 8

Vi har fortfarande 16 par förskapade filer, färdiga att användas när kontrollpunkterna har stängts.

Det finns ett par under konstruktion, som används tills den aktuella kontrollpunkten stängs. Tillsammans med de aktiva kontrollpunktsfilerna ger detta cirka 6,5 GB diskanvändning för 6,5 GB data i minnet. Kom ihåg att index inte sparas på disken, och därför är den totala storleken på disken mindre än storleken i minnet i det här fallet.

Efter demoåterställning

Efter demoåterställningen frigörs inte diskutrymmet omedelbart om det inte finns någon transaktionsarbetsbelastning i systemet och det inte finns några kontrollpunkter för databasen. För att kontrollpunktsfiler ska kunna flyttas genom sina olika faser och så småningom kastas, måste ett antal kontrollpunkter och loggtrunkeringshändelser inträffa för att initiera sammanslagning av kontrollpunktsfiler samt för att initiera sopsamling. Dessa sker automatiskt om du har en transaktionsarbetsbelastning i systemet (och utför regelbundna loggsäkerhetskopior, om du använder fullständig återställningsmodell), men inte när systemet är inaktivt, som i ett demoscenario.

I exemplet, efter demoåterställning, kan du se något i stil med:

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

Vid nästan 12 GB är detta betydligt mer än de 9 GB vi hade före demoåterställningen. Det beror på att vissa sammanslagningar av kontrollpunktsfiler har startats, men några av sammanslagningsmålen ännu inte har installerats och att vissa av källfilerna för sammanslagning ännu inte har rensats, vilket framgår av följande:

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;
tillståndsbeskrivning filtyp_beskrivning räkna storlek på disken i MB
FÖRDEFINIERAD DATA 16 2048
FÖRBEREDD DELTA 16 128
AKTIV DATA 38 5152
AKTIV DELTA 38 1331
SAMMANSLAGNINGSMÅL DATA 7 896
SAMMANSLAGNINGSMÅL DELTA 7 56
SAMMANFOGAD KÄLLA DATA 13 1772
SAMMANFOGAD KÄLLA DELTA 13 455

Mål för sammanslagning installeras och den sammanfogade källan rensas när transaktionsaktiviteter sker i systemet.

Efter en andra demokörning, när 10 miljoner försäljningsorder läggs in efter demoåterställningen, kommer du att se att filerna skapade under den första demokörningen har rensats. Om du kör ovanstående fråga flera gånger medan arbetsbelastningen körs kan du se hur kontrollpunktsfilerna tar sig igenom de olika stegen.

Efter den andra körningen av arbetsbelastningen, som infogar 10 miljoner försäljningsorder, kommer du att se diskutnyttjandet likna det efter den första körningen, även om det inte nödvändigtvis är exakt detsamma, eftersom systemet är dynamiskt till sin natur. Till exempel:

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;
tillståndsbeskrivning filtyp_beskrivning räkna Storlek på disken MB
FÖRSKAPAD Data 16 2048
FÖRSKAPAD DELTA 16 128
BYGGARBETE PÅGÅR DATA 2 268
UNDER KONSTRUKTION DELTA 2 16
AKTIV DATA 41 5608
AKTIV DELTA 41 328

I det här fallet finns det två kontrollpunktsfilpar i tillståndet "under konstruktion", vilket innebär att flera filpar flyttades till tillståndet "under konstruktion", sannolikt på grund av den höga samtidigheten i arbetsbelastningen. Flera samtidiga trådar krävde ett nytt par filer samtidigt och flyttade därför ett par från "förskapade" till "under uppbyggnad".

Nästa steg