In mijn laatste bericht begon ik het proces te schetsen dat ik doorloop bij het afstemmen van zoekopdrachten, met name wanneer ik ontdek dat ik een nieuwe index moet toevoegen of een bestaande moet wijzigen. Tot nu toe hebben we de problematische query geïdentificeerd, de index die ik nodig heb, welke indexen er momenteel op de tafel staan en of die indexen al dan niet worden gebruikt. Zodra we die gegevens hebben, kunnen we doorgaan naar de volgende stappen in het proces.
Stap 5:Wat gebruikt een index
Naast het zien hoe vaak een index wordt gebruikt (of niet), is het handig om te weten welke zoekopdrachten gebruik een index, vooral als ik deze wil samenvoegen met een andere index. Gelukkig heeft Jonathan Kehayias al een query geschreven om te helpen bepalen welke plannen een specifieke index gebruiken. Zijn versie kan worden gebruikt voor de plancache - de enige uitdaging die er is, is dat de informatie van voorbijgaande aard is, dus het is mogelijk dat u niet elke zoekopdracht vastlegt die een bepaalde index gebruikt. Query Store kan daarbij helpen - ik heb zijn zoekopdracht aangepast om dezelfde informatie uit de plannen in Query Store te halen:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED DECLARE @IndexName AS NVARCHAR(128) = N'[IX_Sales_OrderLines_AllocatedStockItems]', @lb AS nchar(1) = N'[', @rb AS nchar(1) = N']'; -- Make sure the name passed is appropriately quoted IF (LEFT(@IndexName, 1) <> @lb AND RIGHT(@IndexName, 1) <> @rb) SET @IndexName = QUOTENAME(@IndexName); --Handle the case where the left or right was quoted manually but not the opposite side IF LEFT(@IndexName, 1) <> @lb SET @IndexName = @rb + @IndexName; IF RIGHT(@IndexName, 1) <> @rb SET @IndexName = @IndexName + @rb; ;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT stmt.value('(@StatementText)[1]', 'varchar(max)') AS SQL_Text, obj.value('(@Database)[1]', 'varchar(128)') AS DatabaseName, obj.value('(@Schema)[1]', 'varchar(128)') AS SchemaName, obj.value('(@Table)[1]', 'varchar(128)') AS TableName, obj.value('(@Index)[1]', 'varchar(128)') AS IndexName, obj.value('(@IndexKind)[1]', 'varchar(128)') AS IndexKind, query_plan FROM ( SELECT query_plan FROM ( SELECT TRY_CONVERT(XML, [qsp].[query_plan]) AS [query_plan] FROM sys.query_store_plan [qsp] ) tp ) AS tab (query_plan) CROSS APPLY query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS batch(stmt) CROSS APPLY stmt.nodes('.//IndexScan/Object[@Index=sql:variable("@IndexName")]') AS idx(obj) OPTION(MAXDOP 1, RECOMPILE);
Het is vermeldenswaard dat dit een ander punt is waar ik mezelf heel diep in een konijnenhol kan bevinden, afhankelijk van het aantal indexen dat ik aan het beoordelen ben en het aantal zoekopdrachten dat ze gebruikt. Indien mogelijk zal ik ook rekening houden met het aantal uitvoeringen (van Query Store of de plancache) om niet alleen wat te begrijpen query een index gebruikt, maar hoe vaak die query wordt uitgevoerd. Dit is waar index-tuning een kunst wordt. Ik kan een belachelijke hoeveelheid gegevens verzamelen... maar ik heb geen oneindige tijd voor analyse, dus ik moet een oordeel vellen over hoeveel vragen ik ga beoordelen.
Stap 6:Testen
Op zijn eenvoudigst betekent het testen van een index het nemen van de problematische query en het vastleggen van de plan- en prestatiegegevens (duur, IO, CPU, enz.), en vervolgens de index maken, de query opnieuw uitvoeren en dezelfde informatie vastleggen. Als de prestaties verbeteren, bent u klaar om te gaan!
Het is zelden zo eenvoudig.
Om te beginnen heb ik vaak minstens twee variaties van een index die ik wil testen, soms meer. Ik begin met mijn baseline, dan maak ik alle indexvariaties, wis de plancache en kijk wat SQL Server kiest. Dan rol ik door en forceer elke index met een hint, waarbij ik het plan en de prestatiestatistieken voor elke uitvoering vastleg. Opmerking:dit veronderstelt dat ik voldoende schijfruimte heb voor alle indexen ... zo niet, dan maak ik ze een voor een en test ik ze. Ten slotte vergelijk ik de cijfers. Als ik gewoon een nieuwe index aan het toevoegen ben, ben ik bijna klaar. Maar als ik een index aan het wijzigen ben of een paar samenvoeg, kan het ingewikkeld worden.
In een ideale wereld, als ik een bestaande index aanpas, vind ik de meest voorkomende/belangrijkste zoekopdrachten die de huidige index gebruiken en krijg ik hun plannen en prestatiestatistieken (dit is gemakkelijk met Query Store). Dan verander ik de index, voer al die zoekopdrachten opnieuw uit en kijk of ik significante veranderingen krijg in de vorm en/of prestaties van het plan.
Als ik twee indexen samenvoeg, doe ik hetzelfde, maar met alle query's die een van beide indexen gebruiken, en test dan opnieuw met de samengevoegde index.
Als ik meerdere indexen voor een tabel toevoeg/wijzig/samenvoeg, dan moet ik alle relevante zoekopdrachten en hun plannen en statistieken ophalen, de indexen wijzigen, dan alle informatie opnieuw ophalen en vergelijken. Dit kan extreem tijdrovend zijn, afhankelijk van het aantal verschillende zoekopdrachten. Dit is waar het een kunstvorm is en je moet bepalen hoeveel queries je echt moet testen. Het is een functie van de frequentie van uitvoering, het belang/relevantie van de query en de tijd die ik beschikbaar/toegewezen heb.
Als ik ten slotte een index aan een tabel toevoeg en geen bestaande verwijder, dan heb ik overhead toegevoegd voor INSERT's, DELETE's en mogelijk UPDATE's. Prestatietests voor deze wijziging zijn mogelijk, maar u hebt een testomgeving nodig en de mogelijkheid om een belastingstest uit te voeren en pre- en post-change-statistieken vast te leggen met betrekking tot duur, IO en CPU.
Het zijn veel vrienden, daarom is het ironisch dat ik er aanvankelijk aan dacht te stellen dat indexafstemming eenvoudig was. Het is misschien niet altijd eenvoudig, maar het is mogelijk. Het is een kwestie van zorgvuldigheid en het bijhouden van alles.
Stap 7:Implementatie
Nadat ik de nieuwe index(en) zo goed mogelijk heb doorgelicht, zijn we klaar voor productie. Ik geef toe dat ik indexwijzigingen als een laag risico beschouw, vooral nieuwe. Als het een probleem is, kunt u het onmiddellijk laten vallen en terugkeren naar de oorspronkelijke staat. Met een scenario voor wijzigen/samenvoegen/laten vallen, wilt u dat alles in een script wordt geschreven, zodat u indexen kunt wijzigen en opnieuw kunt maken om de indexen opnieuw in te stellen. Ik raad altijd aan om indexen in eerste instantie uit te schakelen in plaats van ze te verwijderen, omdat je je dan geen zorgen hoeft te maken over de definitie - als je de index opnieuw moet toevoegen, bouw je hem gewoon opnieuw op.
Samenvatting
Uw methode voor het toevoegen en/of consolideren van indexen kan anders zijn! Net als het afstemmen van zoekopdrachten, is er geen perfect proces. Voor iedereen die nieuw is bij het afstemmen van indexen, biedt dit hopelijk een aanzet tot items om te bekijken en belangrijke overwegingen. Het is onmogelijk om indexen toe te voegen zonder wat overhead toe te voegen - en hier komt de kunst om de hoek kijken:je moet bepalen of het voordeel van de index opweegt tegen de kosten voor aanpassingen.
Index-tuning is een eeuwigdurend, iteratief proces - ik denk niet dat je ooit klaar bent, omdat codewijzigingen, nieuwe tabellen of functionaliteit worden toegevoegd en gegevens in de tabellen veranderen. Kimberly heeft twee berichten (https://www.sqlskills.com/blogs/kimberly/spring-cleaning-your-indexes-part-i/ en https://www.sqlskills.com/blogs/kimberly/spring-cleaning- your-indexes-part-ii/) die praten over het opschonen van uw indexen - het is nu zo goed als tijd om te beginnen! En tot slot, wanneer iemand vraagt:"hoeveel indexen moeten er voor een tabel zijn?" Ik antwoord met zoiets als:"het minste aantal dat je nodig hebt om aan zoveel mogelijk vragen te voldoen." Er is geen magisch getal - ik heb tabellen gezien met nulindexen en ik heb tabellen gezien met meer dan 100 (ik weet zeker dat sommigen van jullie hogere tellingen hebben gezien). Noch nul noch 100 is goed, maar het 'juiste' getal is er een dat je moet bepalen met behulp van de beschikbare gegevens en je ervaring.