sql >> Databasteknik >  >> RDS >> Database

En metod för indexjustering – Del 2

I mitt förra inlägg började jag beskriva processen jag går igenom när jag ställer in frågor – speciellt när jag upptäcker att jag behöver lägga till ett nytt index eller ändra ett befintligt. Fram till denna punkt har vi identifierat den problematiska frågan, indexet jag behöver, vilka index som för närvarande finns på tabellen och om dessa index används eller inte. När vi har den informationen kan vi gå vidare till nästa steg i processen.

Steg 5:Vad använder ett index

Förutom att se hur ofta ett index används (eller inte), är det fördelaktigt att veta vilka frågor använd ett index, särskilt om jag vill slå samman det med ett annat index. Lyckligtvis har Jonathan Kehayias redan skrivit en fråga för att hjälpa till att identifiera vilka planer som använder ett specifikt index. Hans version kan användas för planens cache - den enda utmaningen där är att informationen är övergående, så du kanske inte fångar varje fråga som använder ett visst index. Query Store kan hjälpa till med det – jag har modifierat hans fråga för att få samma information från planerna i Query Store:

  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);

Det är värt att notera att detta är ytterligare en punkt där jag kan befinna mig väldigt djupt i ett kaninhål, beroende på antalet index jag granskar och antalet frågor som använder dem. Om möjligt kommer jag också att överväga antalet körningar (från Query Store eller planens cache) för att inte bara förstå vad fråga använder ett index, men hur ofta den frågan körs. Det är här indextuning blir en konst. Jag kan samla in en löjlig mängd data... men jag har inte oändlig tid för analys, så jag måste göra en bedömning av hur många frågor jag ska granska.

Steg 6:Testning

Som enklast innebär att testa ett index att ta den problematiska frågan och fånga plan- och prestandadata (varaktighet, IO, CPU, etc.), och sedan skapa indexet, köra frågan igen och fånga samma information. Om prestandan förbättras är du igång!

Det är sällan så enkelt.

Till att börja med har jag ofta minst två varianter av ett index som jag vill testa, ibland fler. Jag börjar med min baslinje, sedan skapar jag alla indexvarianter, rensar plancachen och ser vad SQL Server väljer. Sedan rullar jag igenom och tvingar fram varje index med en ledtråd, och fångar upp planen och prestandamåtten för varje exekvering. Notera:detta förutsätter att jag har tillräckligt med diskutrymme för alla index... om inte, skapar jag dem ett i taget och testar. Till sist jämför jag siffrorna. Om jag bara lägger till ett nytt index är jag nästan klar. Men om jag ändrar ett index eller slår ihop ett par kan det bli komplicerat.

I en idealisk värld, om jag ändrar ett befintligt index, hittar jag de vanligaste/viktigaste frågorna som använder det aktuella indexet och får deras planer och prestandamått (detta är enkelt med Query Store). Sedan ändrar jag indexet, kör alla dessa frågor igen och ser om jag får betydande förändringar i planens form och/eller prestanda.

Om jag slår samman två index gör jag samma sak, men med alla frågor som använder något av indexen, och testar sedan igen med det sammanslagna indexet.

Om jag lägger till/ändrar/slår samman flera index för en tabell, måste jag få alla relevanta frågor och deras planer och mätvärden, ändra indexen och sedan hämta all information igen och jämföra. Detta kan vara extremt tidskrävande, beroende på hur många olika frågor som finns. Det är här det är en konstform och du måste bestämma hur många frågor du verkligen behöver testa. Det är en funktion av exekveringsfrekvens, frågans betydelse/relevans och den tid jag har tillgänglig/tilldelad.

Slutligen, om jag lägger till ett index i en tabell och jag inte tar bort några befintliga, så har jag lagt till overhead för INSERT, DELETE och potentiellt UPPDATERINGAR. Prestandatestning av denna förändring är möjlig, men du behöver en testmiljö och förmågan att köra ett belastningstest och fånga mätvärden före och efter förändring relaterade till varaktighet, IO och CPU.

Det är många vänner, och det är därför det är ironiskt att jag först tänkte påstå att indexjustering var lätt. Det kanske inte alltid är enkelt, men det är möjligt. Det är en fråga om flit och att hålla reda på allt.

Steg 7:Implementering

Efter att jag har granskat de nya indexen så mycket som möjligt är vi redo för produktion. Jag erkänner att jag ser indexförändringar som låg risk, särskilt nya. Om det är ett problem kan du släppa det omedelbart och återgå till det ursprungliga tillståndet. Med ett modifiera/sammanfoga/släpp-scenario vill du ha allt skriptat, så att du kan ändra och återskapa index efter behov för att återställa indexen. Jag rekommenderar alltid att du först inaktiverar index istället för att ta bort dem, eftersom du då inte behöver oroa dig för definitionen – om du behöver lägga till indexet tillbaka bygger du helt enkelt om det.

Sammanfattning

Din metod för att lägga till och/eller konsolidera index kan vara annorlunda! Precis som query tuning finns det ingen perfekt process. För alla som är nybörjare med indexjustering ger detta förhoppningsvis en start på saker att granska och viktiga överväganden. Det är omöjligt att lägga till index utan att lägga till en viss mängd omkostnader – och återigen är det här konsten kommer in:du måste avgöra om fördelen med indexet uppväger dess kostnad för ändringar.

Indexjustering är en evig, iterativ process – jag tror aldrig att du är klar, eftersom kodändringar, nya tabeller eller funktionalitet läggs till och data i tabellerna ändras. Kimberly har två inlägg (https://www.sqlskills.com/blogs/kimberly/spring-cleaning-your-indexes-part-i/ och https://www.sqlskills.com/blogs/kimberly/spring-cleaning- your-indexes-part-ii/) som talar om att rensa upp dina index — nu är det dags att komma igång! Och slutligen, när någon frågar, "hur många index ska det finnas för ett bord?" Jag svarar med något i stil med "det minsta antalet du behöver för att tillfredsställa så många frågor som möjligt." Det finns inget magiskt tal — jag har sett tabeller med noll index, och jag har sett tabeller med över 100 (jag är säker på att några av er har sett högre siffror). Varken noll eller 100 är bra, men den "rätta" siffran är ett du måste räkna ut med hjälp av tillgänglig data och din erfarenhet.


  1. SQL-anslutningsfråga

  2. Multi-Statement TVFs i Dynamics CRM

  3. Installera och anslut till PostgreSQL 10 på Ubuntu 16.04

  4. MySQL-handledning – Hantera MySQL-serverloggar:Rotera, komprimera, behålla och ta bort