Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
van toepassing op:SQL Server
Azure SQL Database
Azure SQL Managed Instance
Voor tabellen die zijn geoptimaliseerd voor geheugen, is vereist dat er voldoende geheugen beschikbaar is om alle rijen en indexen in het geheugen te bewaren. Omdat geheugen een eindige resource is, is het belangrijk dat u het geheugengebruik op uw systeem begrijpt en beheert. De onderwerpen in deze sectie hebben betrekking op veelvoorkomende scenario's voor geheugengebruik en -beheer.
Het is belangrijk om een redelijke schatting te maken van de geheugenbehoeften van elke tabel die is geoptimaliseerd voor geheugen, zodat u de server met voldoende geheugen kunt inrichten. Dit geldt voor zowel nieuwe tabellen als tabellen die zijn gemigreerd van schijftabellen. In deze sectie wordt beschreven hoe u een schatting maakt van de hoeveelheid geheugen die u nodig hebt voor een tabel die is geoptimaliseerd voor geheugen.
Als u een migratie van schijftabellen naar tabellen met geoptimaliseerd geheugen overweegt, raadpleegt u Bepalen of een tabel of opgeslagen procedure moet worden overgezet naar In-Memory OLTP voor richtlijnen over welke tabellen het beste kunnen worden gemigreerd. Alle onderwerpen onder Migreren naar In-Memory OLTP- bieden richtlijnen voor het migreren van schijven naar tabellen die zijn geoptimaliseerd voor geheugen.
Basisrichtlijnen voor het schatten van geheugenvereisten
In SQL Server 2016 (13.x) en latere versies is er geen limiet voor de grootte van tabellen die zijn geoptimaliseerd voor geheugen, hoewel de tabellen wel in het geheugen moeten passen. In SQL Server 2014 (12.x) is de ondersteunde gegevensgrootte 256 GB voor SCHEMA_AND_DATA tabellen.
De grootte van een voor geheugen geoptimaliseerde tabel komt overeen met de grootte van de gegevens plus enige rijoverhead. De grootte van de tabel die is geoptimaliseerd voor geheugen komt ongeveer overeen met de grootte van de geclusterde index of heap van de oorspronkelijke schijftabel.
Indexen voor tabellen die zijn geoptimaliseerd voor geheugen, zijn meestal kleiner dan niet-geclusterde indexen op schijftabellen. De grootte van niet-geclusterde indexen is in de volgorde van [primary key size] * [row count]. De grootte van hash-indexen is [bucket count] * 8 bytes.
Wanneer er een actieve workload is, is extra geheugen nodig om rekening te houden met rijversiebeheer en verschillende bewerkingen. De vereiste hoeveelheid geheugen is afhankelijk van de werkbelasting, maar om veilig te zijn, is de aanbeveling om te beginnen met twee keer de verwachte grootte van tabellen en indexen die zijn geoptimaliseerd voor geheugen, en het werkelijke geheugenverbruik te observeren. De overhead voor rijversiebeheer is altijd afhankelijk van de kenmerken van de workload, met name langdurige transacties verhogen de overhead. Voor de meeste workloads die gebruikmaken van grotere databases (bijvoorbeeld groter dan 100 GB), is de overhead meestal beperkt (25 procent of minder).
Zie Geheugenfragmentatie voor meer informatie over potentiële geheugenoverhead in de In-Memory OLTP-engine.
Gedetailleerde berekening van geheugenvereisten
Voorbeeld van tabel die is geoptimaliseerd voor geheugen
Bekijk het volgende tabelschema dat is geoptimaliseerd voor geheugen:
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
Met dit schema bepalen we het minimale geheugen dat nodig is voor deze tabel die is geoptimaliseerd voor geheugen.
Geheugen voor de tabel
Een tabelrij die is geoptimaliseerd voor geheugen heeft drie onderdelen:
Tijdstempels
Rij-koptekst/tijdstempels = 24 bytes.indexaanwijzers
Voor elke hash-index in de tabel heeft elke rij een adresaanwijzer van 8 bytes naar de volgende rij in de index. Omdat er vier indexen zijn, wijst elke rij 32 bytes toe voor indexaanwijzers (een 8-byteaanwijzer voor elke index).Data
De grootte van het gegevensgedeelte van de rij wordt bepaald door de grootte van het type voor elke gegevenskolom op te tellen. In onze tabel hebben we vijf 4-byte gehele getallen, drie tekenkolommen van 50-byte en één tekenkolom van 30-byte. Daarom is het gegevensgedeelte van elke rij 4 + 4 + 4 + 4 + 4 + 50 + 50 + 30 + 50 of 200 bytes.
Hier volgt een grootteberekening voor 5.000.000 rijen (5 miljoen) in een tabel die is geoptimaliseerd voor geheugen. Het totale geheugen dat door gegevensrijen wordt gebruikt, wordt als volgt geschat:
Geheugen voor de rijen van de tabel
In de bovenstaande berekeningen is de grootte van elke rij in de geheugen-geoptimaliseerde tabel 24 + 32 + 200, of 256 bytes. Omdat we 5 miljoen rijen hebben, verbruikt de tabel 5.000.000 * 256 bytes of 1.280.000.000 bytes - ongeveer 1,28 GB.
Geheugen voor indexen
Geheugen voor elke hash-index
Elke hash-index is een hashmatrix van 8-byte-adrespunten. De grootte van de matrix wordt het beste bepaald door het aantal unieke indexwaarden voor die index. In het huidige voorbeeld is het aantal unieke col2-waarden een goed uitgangspunt voor de matrixgrootte voor de t1c2_index. Een hash array die te groot is, verspilt geheugen. Een hash-array die te klein is, vertraagt de prestaties doordat er te veel conflicten optreden door indexwaarden die naar dezelfde indexvermelding hashen.
Hash-indexen zorgen voor zeer snelle gelijkheidszoekacties, zoals:
SELECT * FROM t_hk
WHERE Col2 = 3;
Niet-geclusterde indexen zijn sneller voor bereikzoekacties, zoals:
SELECT * FROM t_hk
WHERE Col2 >= 3;
Als u een tabel op basis van een schijf migreert, kunt u het volgende gebruiken om het aantal unieke waarden voor de index t1c2_index te bepalen.
SELECT COUNT(DISTINCT [Col2])
FROM t_hk;
Als u een nieuwe tabel maakt, moet u een schatting maken van de matrixgrootte of gegevens verzamelen uit uw tests voorafgaand aan de implementatie.
Zie Hash-indexenvoor informatie over hoe hash-indexen werken in In-Memory tabellen die zijn geoptimaliseerd voor OLTP-geheugen.
De grootte van de hash-indexmatrix instellen
De hashmatrixgrootte wordt ingesteld door (bucket_count= value) waarbij value een geheel getal is dat groter is dan nul. Als value geen macht van 2 is, wordt de werkelijke bucket_count naar boven afgerond op de volgende dichtstbijzijnde macht van 2. In onze voorbeeldtabel ( bucket_count = 5000000), omdat 5.000.000 geen macht van 2 is, wordt het werkelijke aantal buckets afgerond op 8.388.608 (2^23). U moet dit getal gebruiken, niet 5.000.000 bij het berekenen van het geheugen dat nodig is voor de hashmatrix.
Dus, in ons voorbeeld, is het geheugen dat nodig is voor elke hash-array:
8.388.608 * 8 = 2^23 * 8 = 2^23 * 2^3 = 2^26 = 67.108.864 of ongeveer 64 MB.
Omdat we drie hash-indexen hebben, is het geheugen dat nodig is voor de hash-indexen 3 * 64 MB = 192 MB.
Geheugen voor niet-geclusterde indexen
Niet-geclusterde indexen worden geïmplementeerd als Bw-trees met de binnenste knooppunten met de indexwaarde en aanwijzers naar volgende knooppunten. Leaf-knooppunten bevatten de indexwaarde en een aanwijzer naar de tabelrij in het geheugen.
In tegenstelling tot hash-indexen hebben niet-geclusterde indexen geen vaste bucketgrootte. De index groeit en verkleint dynamisch met de gegevens.
Geheugen dat nodig is voor niet-geclusterde indexen, kan als volgt worden berekend:
Geheugen toegewezen aan niet-blad knooppunten
Voor een typische configuratie is het geheugen dat aan niet-bladknopen is toegewezen, een klein percentage van het totale geheugen dat gebruikt wordt door de index. Dit is zo klein dat het veilig kan worden genegeerd.geheugen voor bladknooppunten
De bladknooppunten hebben één rij voor elke unieke sleutel in de tabel die verwijst naar de gegevensrijen met die unieke sleutel. Als u meerdere rijen met dezelfde sleutel hebt (dat wil gezegd, u een niet-geclusterde index hebt), is er slechts één rij in het indexbladknooppunt dat verwijst naar een van de rijen met de andere rijen die aan elkaar zijn gekoppeld. Het totale geheugen dat nodig is, kan dus bij benadering worden berekend door:- geheugenVoorNiet-geclusterdIndex = (pointerSize + sum(keyColumnDataTypeSizes)) * rijenMetUniekeSleutels
Niet-geclusterde indexen zijn het beste wanneer ze worden gebruikt voor bereikzoekacties, zoals geïllustreerd door de volgende query:
SELECT * FROM t_hk
WHERE c2 > 5;
Geheugen voor rijversies
Om vergrendelingen te voorkomen, gebruikt In-Memory OLTP optimistische gelijktijdigheid bij het bijwerken of verwijderen van rijen. Dit betekent dat wanneer een rij wordt bijgewerkt, een andere versie van de rij wordt gemaakt. Bovendien zijn verwijderingen logisch: de bestaande rij wordt gemarkeerd als verwijderd, maar niet onmiddellijk verwijderd. Het systeem houdt oude rijversies (inclusief verwijderde rijen) beschikbaar totdat alle transacties die mogelijk de uitvoering van de versie kunnen gebruiken, worden voltooid.
Omdat er op elk moment nog veel meer rijen in het geheugen kunnen zijn die wachten totdat de garbage-collectioncyclus hun geheugen vrijgeeft, moet u voldoende geheugen hebben om deze andere rijen te kunnen herbergen.
Het aantal extra rijen kan worden geschat door het piekaantal rijupdates en verwijderingen per seconde te berekenen en vervolgens te vermenigvuldigen met het aantal seconden dat de langste transactie duurt (minimaal 1).
Deze waarde wordt vervolgens vermenigvuldigd met de rijgrootte om het aantal benodigde bytes voor rijversiebeheer te berekenen.
rowVersions = durationOfLongestTransactionInSeconds * peakNumberOfRowUpdatesOrDeletesPerSecond
Geheugenbehoeften voor verouderde rijen worden vervolgens geschat door het aantal verouderde rijen te vermenigvuldigen met de grootte van een tabelrij die is geoptimaliseerd voor geheugen. Voor meer informatie, zie Geheugen voor de tabel.
memoryForRowVersions = rowVersions * rowSize
Geheugen voor tabelvariabelen
Geheugen dat wordt gebruikt voor een tabelvariabele wordt alleen vrijgegeven wanneer de tabelvariabele buiten het bereik valt. Verwijderde rijen, inclusief rijen die zijn verwijderd als onderdeel van een update, uit een tabelvariabele zijn niet onderhevig aan vuilopruiming. Er wordt geen geheugen vrijgegeven totdat het bereik van de tabelvariabele wordt afgesloten.
Tabelvariabelen die zijn gedefinieerd in een grote SQL-batch in plaats van in een opgeslagen procedure en die in veel transacties worden gebruikt, kunnen een grote hoeveelheid geheugen verbruiken. Omdat ze niet automatisch verzameld zijn door de garbage collector, kunnen verwijderde rijen in een tabelvariabele veel geheugen verbruiken, wat de prestaties kan verminderen, omdat leesbewerkingen langs de verwijderde rijen moeten scannen.
Geheugen voor groei
De vorige berekeningen schatten uw geheugenbehoeften voor de tabel zoals deze momenteel bestaat. Naast dit geheugen moet u de groei van de tabel schatten en voldoende geheugen bieden om die groei mogelijk te maken. Als u bijvoorbeeld 10% groei verwacht, moet u de vorige resultaten met 1,1 vermenigvuldigen om het totale geheugen te verkrijgen dat nodig is voor uw tabel.
Geheugenfragmentatie
Om de overhead van aanroepen voor geheugentoewijzing te voorkomen en de prestaties te verbeteren, vraagt de In-Memory OLTP-engine altijd geheugen aan bij het SQL Server-besturingssysteem (SQLOS) met behulp van blokken van 64 kB, ook wel superblokkeringen genoemd.
Elke superblock bevat alleen geheugentoewijzingen binnen een specifiek groottebereik, ook wel grootteklasse genoemd. Superblock A kan bijvoorbeeld geheugentoewijzingen hebben in de grootteklasse van 1-16 byte, terwijl superblock B geheugentoewijzingen kan hebben in de grootteklasse 17-32 byte, enzovoort.
Superblocks worden standaard ook gepartitioneerd door logische CPU. Dat betekent dat er voor elke logische CPU een afzonderlijke set superblocks is, verder opgesplitst op grootteklasse. Dit vermindert conflicten tussen geheugentoewijzingen tussen aanvragen die worden uitgevoerd op verschillende CPU's.
Wanneer de In-Memory OLTP-engine een nieuwe geheugentoewijzing maakt, wordt eerst geprobeerd om vrij geheugen te vinden in een bestaand superblok voor de aangevraagde grootteklasse en voor de CPU-verwerking van de aanvraag. Als deze poging is geslaagd, neemt de waarde in de used_bytes kolom in sys.dm_xtp_system_memory_consumers voor een specifieke geheugengebruiker toe met de aangevraagde geheugengrootte, maar blijft de waarde in de allocated_bytes kolom hetzelfde.
Als er geen vrij geheugen beschikbaar is in bestaande superblocks, wordt er een nieuw superblock toegewezen en wordt de waarde in de used_bytes verhoogd met de aangevraagde geheugengrootte, terwijl de waarde in de allocated_bytes kolom met 64 KB toeneemt.
Aangezien het geheugen in superblocks in de loop van de tijd wordt toegewezen en de toewijzing ervan ongedaan wordt gemaakt, kan de totale hoeveelheid geheugen die door de In-Memory OLTP-engine wordt verbruikt aanzienlijk groter worden dan de hoeveelheid gebruikt geheugen. Met andere woorden, geheugen kan worden gefragmenteerd.
Garbage collection kan het gebruikte geheugen verminderen, maar het vermindert alleen het toegewezen geheugen als een of meer superblocks leeg worden en gedealloceerd. Dit geldt zowel voor automatische als gedwongen geheugenschoonmaak met behulp van de sys.sp_xtp_force_gc system opgeslagen procedure.
Als de geheugenfragmentatie van de In-Memory OLTP-engine en het toegewezen geheugengebruik hoger worden dan verwacht, kunt u traceringsvlag 9898 inschakelen. Hierdoor verandert het superblokpartitioneringsschema van per CPU in per NUMA-knooppunt, waardoor het totale aantal superblocks en het potentieel voor hoge geheugenfragmentatie wordt verminderd.
Deze optimalisatie is relevanter voor grote machines met veel logische CPU's. Het nadeel van deze optimalisatie is een potentiële toename van conflicten over geheugentoewijzing als gevolg van minder superblocks, waardoor de totale doorvoer van de werkbelasting kan worden verminderd. Afhankelijk van de werklastpatronen kan de doorvoervermindering bij het gebruik van memory-partitionering per NUMA merkbaar of niet merkbaar zijn.