Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
gäller för:SQL Server
Azure SQL Database
Azure SQL Managed Instance
Minnesoptimerade tabeller kräver att det finns tillräckligt med minne för att behålla alla rader och index i minnet. Eftersom minne är en begränsad resurs är det viktigt att du förstår och hanterar minnesanvändning i systemet. Avsnitten i det här avsnittet beskriver vanliga scenarier för minnesanvändning och hantering.
Det är viktigt att ha en rimlig uppskattning av varje minnesoptimerad tabells minnesbehov så att du kan etablera servern med tillräckligt med minne. Det gäller både nya tabeller och tabeller som migrerats från diskbaserade tabeller. I det här avsnittet beskrivs hur du beräknar mängden minne som du behöver för att lagra data för en minnesoptimerad tabell.
Om du överväger en migrering från diskbaserade tabeller till minnesoptimerade tabeller kan du läsa Avgöra om en tabell eller lagrad procedur ska portas till In-Memory OLTP för vägledning om vilka tabeller som är bäst att migrera. Alla avsnitt under Migrera till In-Memory OLTP ge vägledning om migrering från diskbaserade till minnesoptimerade tabeller.
Grundläggande vägledning för att uppskatta minneskrav
I SQL Server 2016 (13.x) och senare versioner finns det ingen gräns för storleken på minnesoptimerade tabeller, även om tabellerna måste få plats i minnet. I SQL Server 2014 (12.x) är datastorleken som stöds 256 GB för SCHEMA_AND_DATA tabeller.
Storleken på en minnesoptimerad tabell motsvarar storleken på data plus vissa omkostnader för radrubriker. Storleken på en minnesoptimerad tabell motsvarar ungefär storleken på ett klustrat index eller en heap för den ursprungliga diskbaserade tabellen.
Indexar för de minnesoptimerade tabeller tenderar att vara mindre än icke-klustrade index på diskbaserade tabeller. Storleken på icke-grupperade index är i storleksordningen [primary key size] * [row count]. Storleken på hash-index är [bucket count] * 8 bytes.
När det finns en aktiv arbetsbelastning krävs extra minne för att ta hänsyn till radversioner och olika åtgärder. Den mängd minne som krävs beror på arbetsbelastningen, men för att vara säker är rekommendationen att börja med två gånger den förväntade storleken på minnesoptimerade tabeller och index och observera den faktiska minnesförbrukningen. Omkostnaderna för radversioner beror alltid på arbetsbelastningens egenskaper – särskilt tidskrävande transaktioner ökar omkostnaderna. För de flesta arbetsbelastningar som använder större databaser (till exempel större än 100 GB) tenderar omkostnaderna att vara begränsade (25 procent eller mindre).
Mer information om potentiella minneskostnader i In-Memory OLTP-motorn finns i Minnesfragmentering.
Detaljerad beräkning av minneskrav
Exempel på minnesoptimerad tabell
Överväg följande minnesoptimerade tabellschema:
CREATE TABLE t_hk
(
col1 int NOT NULL PRIMARY KEY NONCLUSTERED,
col2 int NOT NULL INDEX t1c2_index
HASH WITH (bucket_count = 5000000),
col3 int NOT NULL INDEX t1c3_index
HASH WITH (bucket_count = 5000000),
col4 int NOT NULL INDEX t1c4_index
HASH WITH (bucket_count = 5000000),
col5 int NOT NULL INDEX t1c5_index NONCLUSTERED,
col6 char (50) NOT NULL,
col7 char (50) NOT NULL,
col8 char (30) NOT NULL,
col9 char (50) NOT NULL
) WITH (memory_optimized = on) ;
GO
Med det här schemat ska vi fastställa det minsta minne som behövs för den här minnesoptimerade tabellen.
Minne för tabell
En minnesoptimerad tabellrad har tre delar:
tidsmarkeringar
Radrubrik/tidsstämplar = 24 bytes.Indexpekare
För varje hashindex i tabellen har varje rad en adresspekare på 8 byte till nästa rad i indexet. Eftersom det finns fyra index allokerar varje rad 32 byte för indexpekare (en 8-bytespekare för varje index).data
Storleken på datadelen av raden bestäms genom att du summerar typstorleken för varje datakolumn. I vår tabell har vi fem heltal med 4 byte, tre kolumner med 50 byte och en kolumn på 30 byte. Därför är datadelen av varje rad 4 + 4 + 4 + 4 + 4 + 50 + 50 + 30 + 50 eller 200 byte.
Följande är en storleksberäkning för 5 000 000 (5 miljoner) rader i en minnesoptimerad tabell. Det totala minnet som används av datarader uppskattas på följande sätt:
Minne för tabellens rader
Från ovanstående beräkningar är storleken på varje rad i den minnesoptimerade tabellen 24 + 32 + 200 eller 256 byte. Eftersom vi har 5 miljoner rader förbrukar tabellen 5 000 000 * 256 byte eller 1 280 000 000 byte – cirka 1,28 GB.
Minne för index
Minne för varje hashindex
Varje hashindex är en hashmatris med 8 bytes adresspekare. Matrisens storlek bestäms bäst av antalet unika indexvärden för indexet. I det aktuella exemplet är antalet unika Col2-värden en bra startpunkt för matrisstorleken för t1c2_index. En hash-matris som är för stor slösar minne. En hashmatris som är för liten försämrar prestandan eftersom det uppstår för många kollisioner där indexvärden hashar till samma indexpost.
Hash-index uppnår mycket snabba likhetssökningar som:
SELECT * FROM t_hk
WHERE Col2 = 3;
Icke-grupperade index är snabbare för intervallsökningar, till exempel:
SELECT * FROM t_hk
WHERE Col2 >= 3;
Om du migrerar en diskbaserad tabell kan du använda följande för att fastställa antalet unika värden för indexet t1c2_index.
SELECT COUNT(DISTINCT [Col2])
FROM t_hk;
Om du skapar en ny tabell måste du beräkna matrisstorleken eller samla in data från testningen före distributionen.
Information om hur hashindex fungerar i In-Memory minnesoptimerade OLTP-tabeller finns i Hash Indexes.
Ange storleken på hash-indexmatrisen
Storleken på hash-matrisen anges av (bucket_count= value) där value är ett heltalsvärde som är större än noll. Om value inte är en effekt på 2 avrundas den faktiska bucket_count upp till nästa närmaste effekt på 2. I vårt exempel på tabellen (bucket_count = 5000000), eftersom 5 000 000 inte är en potens av 2, avrundas det faktiska antalet hinkar uppåt till 8 388 608 (2^23). Du måste använda det här talet, inte 5 000 000 när du beräknar minne som behövs av hash-matrisen.
I vårt exempel är alltså det minne som behövs för varje hash-matris:
8 388 608 * 8 = 2^23 * 8 = 2^23 * 2^3 = 2^26 = 67 108 864 eller cirka 64 MB.
Eftersom vi har tre hash-index är det minne som behövs för hash-indexen 3 * 64 MB = 192 MB.
Minne för icke-grupperade index
Icke-grupperade index implementeras som Bw-trees med de inre noderna som innehåller indexvärdet och pekarna till efterföljande noder. Lövnoder innehåller indexvärdet och en pekare till tabellraden i minnet.
Till skillnad från hashindex har icke-grupperade index inte en fast bucketstorlek. Indexet växer och krymper dynamiskt med data.
Minne som behövs av icke-grupperade index kan beräknas på följande sätt:
Minne allokerat till icke-bladnoder
För en typisk konfiguration är det minne som allokeras till icke-bladnoder en liten procentandel av det totala minnet som används av indexet. Detta är så litet att det säkert kan ignoreras.Minne för bladnoder
Lövnoderna har en rad för varje unik nyckel i tabellen som pekar på dataraderna med den unika nyckeln. Om du har flera rader med samma nyckel (det vill säga, du har ett icke-unikt, icke-klustrat index), finns det bara en rad i indexbladens nod som pekar på en av raderna, medan de andra raderna är länkar till varandra. Därför kan det totala minne som krävs approximeras genom att:- minneFörIckeKlustaradIndex = (pekareStorlek + summa(nyckelKolumnDatatypStorlekar)) * raderMedUnikaNycklar
Icke-grupperade index är bäst när de används för intervallsökningar, vilket exemplifieras av följande fråga:
SELECT * FROM t_hk
WHERE c2 > 5;
Minne för radversion
För att undvika lås använder In-Memory OLTP optimistisk samtidighet vid uppdatering eller borttagning av rader. Det innebär att när en rad uppdateras skapas en annan version av raden. Dessutom är borttagningar logiska – den befintliga raden markeras som borttagen, men tas inte bort omedelbart. Systemet håller gamla radversioner (inklusive borttagna rader) tillgängliga tills alla transaktioner som kan använda versionen har avslutats.
Eftersom det kan finnas många fler rader i minnet när som helst som väntar på att skräpinsamlingscykeln ska frigöra deras minne, måste du ha tillräckligt med minne för att rymma dessa andra rader.
Antalet extra rader kan beräknas genom att beräkna det högsta antalet raduppdateringar och borttagningar per sekund och sedan multiplicera det med antalet sekunder som den längsta transaktionen tar (minst 1).
Värdet multipliceras sedan med radstorleken för att få det antal byte du behöver för radversionshantering.
rowVersions = durationOfLongestTransactionInSeconds * peakNumberOfRowUpdatesOrDeletesPerSecond
Minnesbehovet för inaktuella rader beräknas sedan genom att multiplicera antalet inaktuella rader med storleken på en minnesoptimerad tabellrad. Mer information finns i Minne för tabellen.
memoryForRowVersions = rowVersions * rowSize
Minne för tabellvariabler
Minne som används för en tabellvariabel frigörs endast när tabellvariabeln hamnar utanför omfånget. Borttagna rader, inklusive rader som tagits bort som en del av en uppdatering, från en tabellvariabel omfattas inte av skräpinsamling. Inget minne frigörs förrän tabellvariabeln avslutar omfånget.
Tabellvariabler som definieras i en stor SQL-batch i stället för i en lagrad procedur och som används i många transaktioner kan förbruka en stor mängd minne. Eftersom de inte samlas in genom sophantering kan borttagna rader i en tabellvariabel använda mycket minne och försämra prestanda, eftersom läsåtgärderna måste skanna förbi de borttagna raderna.
Minne för tillväxt
I de tidigare beräkningarna beräknas dina minnesbehov för tabellen eftersom den för närvarande finns. Förutom det här minnet måste du uppskatta tabellens tillväxt och tillhandahålla tillräckligt med minne för att hantera den tillväxten. Om du till exempel förväntar dig 10% tillväxt måste du flera tidigare resultat med 1,1 för att få det totala minne som behövs för tabellen.
Minnesfragmentering
För att undvika kostnader för minnesallokeringsanrop och förbättra prestandan begär In-Memory OLTP-motorn alltid minne från SQL Server-operativsystemet (SQLOS) med 64 KB-block, som kallas superblock.
Varje superblock innehåller endast minnesallokeringar inom ett visst storleksintervall, som kallas storleksklass. Superblock A kan till exempel ha minnesallokeringar i storleksklassen 1–16 byte, medan superblock B kan ha minnesallokeringar i storleksklassen 17–32 byte och så vidare.
Som standard partitioneras även superblock av logisk PROCESSOR. Det innebär att det för varje logisk PROCESSOR finns en separat uppsättning superblock, ytterligare uppdelade efter storleksklass. Detta minskar konkurrensen om minnesallokering mellan begäranden som körs på olika processorer.
När den In-Memory OLTP-motorn gör en ny minnesallokering försöker den först hitta ledigt minne i en befintlig superblockering för den begärda storleksklassen och för processorbearbetningen av begäran. Om det här försöket lyckas ökar värdet i used_bytes kolumnen i sys.dm_xtp_system_memory_consumers för en specifik minneskonsument med den begärda minnesstorleken allocated_bytes , men värdet i kolumnen förblir detsamma.
Om det inte finns något ledigt minne i befintliga superblock allokeras ett nytt superblock och värdet i used_bytes ökar med den begärda minnesstorleken, medan värdet i kolumnen ökar med 64 KB.
Med tiden, när minne i superblock allokeras och frigörs, kan den totala mängden minne som förbrukas av In-Memory OLTP-motorn bli betydligt större än mängden använt minne. Med andra ord kan minnet bli fragmenterat.
Skräpinsamling kan minska det använda minnet, men det minskar bara det allokerade minnet om en eller flera superblock blir tomma och frigörs. Detta gäller både automatisk och forcerad sophantering med hjälp av den sys.sp_xtp_force_gc lagrade systemproceduren.
Om minnesfragmentering och allokerad minnesanvändning för In-Memory OLTP-motorn blir högre än förväntat kan du aktivera spårningsflagga 9898. Detta ändrar superblockspartitioneringsschemat från per CPU till per NUMA-nod, vilket minskar det totala antalet superblock och risken för hög minnesfragmentering.
Den här optimeringen är mer relevant för stora datorer med många logiska processorer. Kompromissen med den här optimeringen är en potentiell ökning av minnesallokeringskonkurrensen till följd av färre superblock, vilket kan minska den övergripande genomströmningen av arbetsbelastningen. Beroende på arbetsbelastningsmönster kan dataflödesminskning från användning av minnespartitionering per NUMA vara märkbar eller inte.