sql >> Database >  >> RDS >> Database

Scans van toewijzingsorders

Wanneer een uitvoeringsplan een scan van een b-tree-indexstructuur bevat, kan de opslagengine mogelijk kunnen kiezen tussen twee fysieke toegangsstrategieën wanneer het plan wordt uitgevoerd:

  1. Volg de index b-boomstructuur; of,
  2. vind pagina's met behulp van interne paginatoewijzingsinformatie.

Waar een keuze beschikbaar is, neemt de opslag-engine de runtime-beslissing over elke uitvoering. Een hercompilatie van een plan is niet nodig is om van gedachten te veranderen.

De b-tree-strategie begint bij de wortel van de boom, daalt af naar een uiterste rand van het bladniveau (afhankelijk van of de scan voorwaarts of achterwaarts is), en volgt vervolgens paginalinks op bladniveau totdat het andere uiteinde van de index is bereikt . De toewijzingsstrategie maakt gebruik van Index Allocation Map (IAM)-structuren om databasepagina's te lokaliseren die aan de index zijn toegewezen. Elke IAM-pagina wijst toewijzingen toe aan een interval van 4 GB in een enkel fysiek databasebestand, dus het scannen van de IAM-ketens die aan een index zijn gekoppeld, heeft de neiging om toegang te krijgen tot indexpagina's in fysieke bestandsvolgorde (tenminste voor zover SQL Server kan zien).

De belangrijkste verschillen tussen de twee strategieën zijn:

  1. Een b-tree-scan kan rijen in indexsleutelvolgorde aan de queryprocessor leveren; een IAM-gestuurde scan kan dat niet;
  2. een b-tree-scan kan mogelijk geen grote read-ahead I/O-verzoeken afgeven als logisch aaneengesloten indexpagina's niet ook fysiek aaneengesloten zijn (bijvoorbeeld als gevolg van paginasplitsing in de index).

Voor een index is altijd een b-tree scan beschikbaar. De vaak aangehaalde voorwaarden voor het beschikbaar zijn van scans van toewijzingsorders zijn:

  1. Het zoekplan moet een ongeordende scan van de index mogelijk maken;
  2. de index moet minimaal 64 pagina's groot zijn; en,
  3. ofwel een TABLOCK of NOLOCK hint moet worden opgegeven.

De eerste voorwaarde betekent simpelweg dat de query-optimizer de scan moet hebben gemarkeerd met de Ordered:False eigendom. De scan markeren Ordered:False betekent dat correcte resultaten van het uitvoeringsplan niet vereisen de scan om rijen terug te geven in de volgorde van de indexsleutel (hoewel dit mogelijk is als dit handig of anderszins noodzakelijk is).

De tweede voorwaarde (grootte) is alleen van toepassing op SQL Server 2005 en later. Het weerspiegelt het feit dat er bepaalde opstartkosten zijn voor het uitvoeren van een IAM-gestuurde scan, dus er moet een minimum aantal pagina's zijn om de potentiële besparingen terug te betalen om de initiële investering terug te betalen. De "64 pagina's" verwijst naar de waarde van data_pages voor de IN_ROW_DATA alleen toewijzingseenheid, zoals gerapporteerd in sys.allocation_units.

Natuurlijk kan er alleen een uitbetaling zijn van een scan van een toewijzingsopdracht als de mogelijk grotere read-ahead-overwegingen eigenlijk spelen een rol, maar SQL Server houdt momenteel geen rekening met deze factor. Het houdt met name geen rekening met hoeveel van de index zich momenteel in het geheugen bevindt, en het maakt ook niet uit hoe gefragmenteerd de index is.

De derde voorwaarde is waarschijnlijk de minst volledige beschrijving in de lijst. Hints zijn in feite niet vereist , hoewel ze kunnen worden gebruikt om aan de echte vereisten te voldoen:de gegevens moeten gegarandeerd zijn dat ze niet veranderen tijdens de scan, of (meer controversieel) we moeten aangeven dat het ons niet kan schelen over mogelijk onnauwkeurige resultaten, door de scan uit te voeren op het gelezen niet-vastgelegde isolatieniveau.

Ook met deze verduidelijkingen is de lijst met voorwaarden voor een allocatie-geordende scan nog niet compleet. Er zijn een aantal belangrijke kanttekeningen en uitzonderingen, waar we binnenkort op zullen ingaan.

Demo

De volgende query gebruikt de voorbeelddatabase van AdventureWorks:

CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P;

Merk op dat de tabel Persoon 3.869 pagina's bevat. Het (werkelijke) plan na de uitvoering is als volgt (getoond in SQL Sentry Plan Explorer):

Wat betreft de scanvereisten voor toewijzingsorders die we tot nu toe hebben:

  • Het abonnement heeft de vereiste Ordered:False eigendom; en,
  • de tabel heeft meer dan 64 pagina's; maar,
  • we hebben niets gedaan om ervoor te zorgen dat de gegevens tijdens de scan niet kunnen veranderen. Ervan uitgaande dat onze sessie de standaard read commit gebruikt isolatieniveau, de scan wordt niet uitgevoerd op de read uncommitted isolatieniveau ook niet.

Als gevolg hiervan zouden we verwachten dat deze scan wordt uitgevoerd door de b-tree te scannen in plaats van IAM-gestuurd te zijn. De zoekopdrachtresultaten geven aan dat dit waarschijnlijk waar is:

De rijen worden geretourneerd in geclusterde indexsleutelvolgorde (door BusinessEntityID ). Ik moet duidelijk vermelden dat deze resultaatvolgorde absoluut niet gegarandeerd is , en er mag niet op worden vertrouwd. Geordende resultaten worden alleen gegarandeerd door een geschikte ORDER BY op het hoogste niveau clausule.

Desalniettemin is de waargenomen uitvoervolgorde het indirecte bewijs dat de scan deze keer is uitgevoerd door de geclusterde index b-boomstructuur te volgen. Als er meer bewijs nodig is, kunnen we een debugger toevoegen en kijken naar het codepad dat SQL Server uitvoert tijdens de scan:

De call-stack toont duidelijk de scan die de b-tree volgt.

Een hint voor tafelvergrendeling toevoegen

We passen de query nu aan om een ​​table-lock hint op te nemen:

CHECKPOINT;
DBCC DROPCLEANBUFFERS;
 
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P
    WITH (TABLOCK);

Op het standaard vergrendelingsniveau voor leescommittatie, voorkomt de gedeelde vergrendeling op tabelniveau mogelijke gelijktijdige wijzigingen aan de gegevens. Nu aan alle drie de voorwaarden voor IAM-gestuurde scans is voldaan, zouden we nu verwachten dat SQL Server een allocatie-volgorde-scan gebruikt. Het uitvoeringsplan is hetzelfde als voorheen, dus ik zal het niet herhalen, maar de queryresultaten zien er zeker anders uit:

De resultaten zijn blijkbaar nog steeds gerangschikt op BusinessEntityID , maar het uitgangspunt (10866) is anders. Inderdaad, als we door de resultaten scrollen, komen we al snel secties tegen die duidelijk niet op volgorde staan:

De gedeeltelijke volgorde is te wijten aan het feit dat de scan van de toewijzingsvolgorde een hele indexpagina tegelijk verwerkt. De resultaten binnen een pagina worden geretourneerd in volgorde van de indexsleutel, maar de volgorde van de gescande pagina's is nu anders. Nogmaals, ik moet benadrukken dat de resultaten er voor u anders uit kunnen zien:er is geen garantie voor uitvoervolgorde, zelfs niet binnen een pagina, zonder een ORDER BY op het hoogste niveau op de oorspronkelijke zoekopdracht.

Ter vergelijking met de eerder getoonde call-stack:dit is een stacktracering die is verkregen terwijl SQL Server de query aan het verwerken was met de TABLOCK hint:

Een beetje verder door de uitvoering stappen:

Het is duidelijk dat SQL Server een scan op basis van toewijzing uitvoert wanneer de tabelvergrendeling is opgegeven. Het is jammer dat er in een post-uitvoeringsplan niet wordt aangegeven welk type scan tijdens runtime is gebruikt. Ter herinnering:het type scan wordt gekozen door de opslagengine en kan tussen uitvoeringen wisselen zonder een hercompilatie van het plan.

Andere manieren om aan de derde voorwaarde te voldoen

Ik zei eerder dat om een ​​IAM-gestuurde scan te krijgen, we ervoor moeten zorgen dat de gegevens onder de scan niet kunnen veranderen terwijl deze aan de gang is, of we moeten de query uitvoeren op het lees-niet-vastgelegde isolatieniveau. We hebben gezien dat een tabelvergrendelingshint voor het vergrendelen van leescommitted-isolatie voldoende is om aan de eerste van deze vereisten te voldoen, en het is gemakkelijk om aan te tonen dat het gebruik van een NOLOCK/READUNCOMMITTED hint maakt ook een scan van de toewijzingsvolgorde mogelijk met de demo-query.

In feite zijn er veel manieren om aan de derde voorwaarde te voldoen, waaronder:

  • De index wijzigen om alleen tafelvergrendelingen toe te staan;
  • de database alleen-lezen maken (zodat gegevens gegarandeerd niet veranderen); of,
  • de sessie wijzigen isolatieniveau tot READ UNCOMMITTED .

Er zijn echter veel interessantere variaties op dit thema, waardoor we de drie eerder genoemde voorwaarden moeten aanpassen...

Isolatieniveaus voor rijversies

Schakel read Committed snapshot isolation (RCSI) in op de AdventureWorks-database en voer de test uit met de TABLOCK hint nogmaals (bij read commit isolation):

ALTER DATABASE AdventureWorks2012
SET READ_COMMITTED_SNAPSHOT ON
    WITH ROLLBACK IMMEDIATE;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P
    WITH (TABLOCK);
GO
ALTER DATABASE AdventureWorks2012
SET READ_COMMITTED_SNAPSHOT OFF
    WITH ROLLBACK IMMEDIATE;

Met RCSI actief, een geïndexeerd scan wordt gebruikt met TABLOCK , niet de allocatie-order scan die we net eerder zagen. De reden is de TABLOCK hint specificeert een gedeelde vergrendeling op tabelniveau, maar met RCSI ingeschakeld, geen gedeelde vergrendelingen zijn genomen. Zonder de gedeelde tabelvergrendeling hebben we niet voldaan aan de vereiste om gelijktijdige wijzigingen aan de gegevens te voorkomen terwijl de scan wordt uitgevoerd, dus een scan op basis van toewijzing kan niet worden gebruikt.

Het is echter mogelijk om een ​​scan op volgorde van toewijzing te realiseren wanneer RCSI is ingeschakeld. Een manier is om een ​​TABLOCKX . te gebruiken hint (voor een exclusief op tafelniveau lock) in plaats van TABLOCK . We kunnen ook de TABLOCK . behouden hint en voeg er nog een toe, zoals READCOMMITTEDLOCK , of REPEATABLE READ of SERIALIZABLE … enzovoort. Al deze werken door de mogelijkheid van gelijktijdige wijzigingen te voorkomen door een gedeelde tafelvergrendeling te nemen, ten koste van het verliezen van de voordelen van RCSI . We kunnen ook nog steeds een scan van de toewijzingsvolgorde bereiken met een NOLOCK of READUNCOMMITTED hint natuurlijk.

De situatie onder snapshot-isolatie (SI) lijkt erg op RCSI en wordt om ruimteredenen niet in detail onderzocht.

TABELSAMPLE altijd* voert een scan van de toewijzingsvolgorde uit

De TABLESAMPLE clausule is een interessante uitzondering op veel van de dingen die we tot nu toe hebben besproken.

Specificeren van een TABLESAMPLE clausule altijd* resulteert in een scan van de toewijzingsvolgorde, zelfs onder RCSI of SI, en zelfs zonder hints. Voor alle duidelijkheid:de scan van de toewijzingsvolgorde die het resultaat is van het gebruik van TABLESAMPLE behoudt RCSI/SI-semantiek – de scan gebruikt rijversies en lezen blokkeert schrijven niet (en vice versa).

Een tweede verrassing is dat TABLESAMPLE altijd* voert een IAM-gestuurde scan uit zelfs als de tabel minder dan 64 pagina's heeft . Dit is logisch omdat de documentatie op zijn minst aangeeft dat het SYSTEM steekproefmethode maakt gebruik van de IAM-structuur (er zit dus niets anders op dan een scan van de toewijzingsvolgorde te doen):

SYSTEEM Is een implementatie-afhankelijke steekproefmethode gespecificeerd door ISO-normen. In SQL Server is dit de enige beschikbare steekproefmethode en wordt deze standaard toegepast. SYSTEM past een op pagina's gebaseerde steekproefmethode toe waarbij een willekeurige set pagina's uit de tabel wordt gekozen voor de steekproef, en alle rijen op die pagina's worden geretourneerd als de voorbeeldsubset.

* Er treedt een uitzondering op als de ROWS of PERCENT specificatie in de TABLESAMPLE clausule werkt om 100% van de tabel te betekenen. Meer ROWS specificeren dan de metadata aangeeft die momenteel in de tabel staan, werken ook niet. Gebruik TABLESAMPLE SYSTEM (100 PERCENT) of equivalent zal niet forceer een toewijzingsvolgorde-scan.

CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P
    TABLESAMPLE SYSTEM (50 ROWS)
    REPEATABLE (12345678)
    --WITH (TABLOCK);

Resultaten:

Het effect van TOP en SET ROWCOUNT

Kortom, geen van beide heeft invloed op de beslissing om al dan niet gebruik te maken van een verdeelvolgordescan. Dit lijkt misschien verrassend in gevallen waarin het "duidelijk" is dat er minder dan 64 pagina's worden gescand.

De volgende zoekopdrachten gebruiken bijvoorbeeld allebei een IAM-gestuurde scan om 5 rijen van een scan te retourneren:

SELECT TOP (5)
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P WITH (TABLOCK)
 
SET ROWCOUNT 5;
 
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P WITH (TABLOCK)
 
SET ROWCOUNT 0;

De resultaten zijn voor beide hetzelfde:

Dit betekent dat TOP en SET ROWCOUNT zoekopdrachten misschien de overheadkosten van het opzetten van een scan voor toewijzingsvolgorde, zelfs als er minder dan 64 pagina's worden gescand. Ter beperking kunnen complexere TOP-query's met selectieve predikaten die in de scan worden geduwd, nog steeds profiteren van een scan met toewijzingsvolgorde. Als de scan 10.000 pagina's moet verwerken om de eerste 5 rijen te vinden die overeenkomen, kan een scan met toewijzingsvolgorde nog steeds een overwinning zijn.

Voorkomen van alle* scans van de toewijzingsvolgorde voor de hele instantie

Dit is waarschijnlijk niet iets dat u met opzet zou doen, maar er is een serverinstelling die scans op de toewijzingsvolgorde voor alle* gebruikersquery's in alle databases verhindert.

Hoe onwaarschijnlijk het ook lijkt, de instelling in kwestie is de configuratieoptie voor de cursordrempelserver, die de volgende beschrijving heeft in Books Online:

De optie cursordrempel specificeert het aantal rijen in de cursorset waarop cursortoetsensets asynchroon worden gegenereerd. Wanneer cursors een keyset voor een resultatenset genereren, schat de query-optimizer het aantal rijen dat voor die resultatenset wordt geretourneerd. Als de query-optimizer schat dat het aantal geretourneerde rijen groter is dan deze drempel, wordt de cursor asynchroon gegenereerd, zodat de gebruiker rijen van de cursor kan ophalen terwijl de cursor gevuld blijft. Anders wordt de cursor synchroon gegenereerd en wacht de query totdat alle rijen zijn geretourneerd.

Als de cursor threshold optie is ingesteld op iets anders dan –1 (de standaard), zullen er geen scans op de toewijzingsvolgorde plaatsvinden voor gebruikersquery's in een database op de SQL Server-instantie.

Met andere woorden, als asynchrone cursorpopulatie is ingeschakeld, zijn er geen IAM-gestuurde scans voor u.

* De uitzondering is (niet-100%) TABLESAMPLE vragen. De interne zoekopdrachten die door het systeem worden gegenereerd voor het maken van statistieken en het bijwerken van statistieken, blijven ook gebruikmaken van scans op basis van toewijzing.

CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
 
-- WARNING! Disables allocation-order scans instance-wide
EXECUTE sys.sp_configure 
    @configname = 'cursor threshold',
    @configvalue = 5000;
 
RECONFIGURE WITH OVERRIDE;
GO
 
-- Would normally result in an allocation-order scan
SELECT
    P.BusinessEntityID,
    P.PersonType
FROM Person.Person AS P
    WITH (READUNCOMMITTED);
GO
 
-- Reset to default allocation-order scans
EXECUTE sys.sp_configure 
    @configname = 'cursor threshold',
    @configvalue = -1;
 
RECONFIGURE WITH OVERRIDE;

Resultaten (geen scan van toewijzingsvolgorde):

Men kan alleen maar raden dat asynchrone cursorpopulatie om de een of andere reden niet goed werkt met scans op toewijzingsvolgorde. Het is volkomen onverwacht dat deze beperking van invloed zou zijn op alle zoekopdrachten van gebruikers zonder cursor ook wel. Misschien is het voor SQL Server te moeilijk om te detecteren of een query wordt uitgevoerd als onderdeel van een extern uitgegeven API-cursor? Wie weet.

Het zou leuk zijn als deze bijwerking ergens officieel werd gedocumenteerd, hoewel het moeilijk is om precies te weten waar het in Books Online naartoe moet. Ik vraag me af hoeveel productiesystemen er zijn die hierdoor geen allocatie-order scans gebruiken? Misschien niet veel, maar je weet maar nooit.

Om alles af te ronden, volgt hier een samenvatting. Een scan op volgorde van toewijzing is beschikbaar als:

  1. De serveroptie cursor threshold is ingesteld op –1 (de standaard); en,
  2. de scanoperator van het queryplan heeft de Ordered:False eigendom; en,
  3. het totaal data_pages van de IN_ROW_DATA toewijzingseenheden is minimaal 64; en,
  4. ofwel:
    1. SQL Server heeft een acceptabele garantie dat gelijktijdige wijzigingen onmogelijk zijn; of,
    2. de scan wordt uitgevoerd op het lees-niet-vastgelegde isolatieniveau.

Ongeacht al het bovenstaande, een scan met een TABLESAMPLE clausule maakt altijd gebruik van op toewijzing geordende scans (met de enige technische uitzondering die in de hoofdtekst wordt vermeld).


  1. Meerdere mysql INSERT-instructies in één query php

  2. LEFT JOIN alleen eerste rij

  3. Gedeeltelijke unieke index maken met sqlalchemy op Postgres

  4. Oracle ODP.NET versie agnostisch alternatief