В последната си публикация започнах да очертавам процеса, през който преминавам, когато настройвам заявки – особено когато открия, че трябва да добавя нов индекс или да променя съществуващ. До този момент идентифицирахме проблемната заявка, индекса, който ми трябва, какви индекси съществуват в момента в таблицата и дали тези индекси се използват или не. След като имаме тези данни, можем да преминем към следващите стъпки в процеса.
Стъпка 5:Какво използва индекс
Освен че виждате колко често се използва даден индекс (или не), е полезно да знаете какви заявки използвам индекс, особено ако искам да го слея с друг индекс. За щастие Джонатан Кехайяс вече е написал заявка, за да помогне да се идентифицира кои планове използват конкретен индекс. Неговата версия може да се използва за кеша на плана – единственото предизвикателство там е, че информацията е преходна, така че може да не улавяте всяка заявка, която използва конкретен индекс. Query Store може да помогне с това – промених заявката му, за да получа същата информация от плановете в 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);
Струва си да се отбележи, че това е друга точка, в която мога да се озова много дълбоко в заешка дупка, в зависимост от броя на индексите, които преглеждам, и броя на заявките, които ги използват. Ако е възможно, ще взема предвид и броя на изпълнението (от Query Store или кеша на плана), за да не разбирам само какво заявката използва индекс, но колко често се изпълнява тази заявка. Това е мястото, където настройката на индекса се превръща в изкуство. Мога да събера смешно количество данни... но нямам безкрайно време за анализ, така че трябва да преценя колко заявки ще прегледам.
Стъпка 6:Тестване
Най-просто, тестването на индекс означава приемане на проблемната заявка и улавяне на данните за плана и производителността (продължителност, IO, CPU и т.н.), а след това създаване на индекса, повторно изпълнение на заявката и улавяне на същата информация. Ако производителността се подобри, можете да тръгнете!
Рядко е толкова просто.
Като начало, често имам поне два варианта на индекс, който искам да тествам, понякога повече. Започвам с моята базова линия, след това създавам всички варианти на индекса, изчиствам кеша на плана и виждам какво избира SQL Server. След това превъртам и принуждавам всеки индекс с намек, улавяйки плана и показателите за ефективност за всяко изпълнение. Забележка:това предполага, че имам достатъчно дисково пространство за всички индекси... ако не, тогава ги създавам един по един и тествам. Накрая сравнявам числата. Ако просто добавям нов индекс, почти съм готов. Но ако променя индекс или обединявам двойка, може да стане сложно.
В идеалния свят, ако променя съществуващ индекс, намирам най-честите/важни заявки, които използват текущия индекс и получавам техните планове и показатели за ефективност (това е лесно с Query Store). След това променям индекса, стартирам всички тези заявки отново и виждам дали ще получа значителни промени във формата на плана и/или производителността.
Ако слея два индекса, правя същото нещо, но с всички заявки, които използват всеки индекс, и след това тествам отново с обединения индекс.
Ако добавям/променям/сливам множество индекси за таблица, тогава трябва да получа всички подходящи заявки и техните планове и показатели, да променя индексите, след което да получа цялата информация отново и да сравня. Това може да отнеме много време, в зависимост от това колко различни заявки има. Тук това е форма на изкуство и трябва да определите колко заявки наистина трябва да тествате. Това е функция от честотата на изпълнение, важността/уместността на заявката и времето, което имам на разположение/разпределено.
И накрая, ако добавя индекс към таблица и не премахна съществуващите, тогава съм добавил допълнителни разходи за INSERT, DELETE и потенциално UPDATE. Тази промяна е възможна за тестване на производителността, но ви е необходима тестова среда и възможност за провеждане на тест за натоварване и заснемане на показатели преди и след промяната, свързани с продължителност, IO и CPU.
Има много приятели, поради което е ирония, че първоначално се сетих да заявя, че настройката на индекса е лесна. Може не винаги да е просто, но е възможно. Това е въпрос на старание и следене на всичко.
Стъпка 7:Внедряване
След като проверих новите индекси, доколкото е възможно, ние сме готови за производство. Признавам, че разглеждам промените в индекса като нискорискови, особено новите. Ако е проблем, можете да го пуснете незабавно и да се върнете към първоначалното състояние. Със сценарий за промяна/сливане/изпускане, вие искате всичко да е скриптирано, така че можете да променяте и пресъздавате индекси, ако е необходимо, за да нулирате индексите. Винаги препоръчвам първоначално да деактивирате индексите, вместо да ги изпускате, тъй като тогава не е нужно да се притеснявате за дефиницията – ако трябва да добавите индекса обратно, просто го изграждате отново.
Резюме
Вашият метод за добавяне и/или консолидиране на индекси може да е различен! Точно като настройката на заявката, няма перфектен процес. За всеки, който е начинаещ в настройката на индекса, това се надяваме да предостави начало на елементи за преглед и важни съображения. Невъзможно е да се добавят индекси, без да се добавят допълнителни разходи – и отново тук идва изкуството:трябва да определите дали ползата от индекса надвишава цената му за модификации.
Настройката на индекса е постоянен, итеративен процес – не мисля, че някога сте свършили, защото се променят кода, добавят се нови таблици или функционалност и данните в таблиците се променят. Кимбърли има две публикации (https://www.sqlskills.com/blogs/kimberly/spring-cleaning-your-indexes-part-i/ и https://www.sqlskills.com/blogs/kimberly/spring-cleaning- your-indexes-part-ii/), които говорят за почистване на вашите индекси — сега е подходящо време да започнете! И накрая, когато някой попита „колко индекса трябва да има за таблица?“ Отговарям с нещо от рода на „най-малкото число, от което се нуждаете, за да удовлетворите възможно най-много заявки“. Няма магическо число — виждал съм таблици с нулеви индекси и съм виждал таблици с над 100 (сигурен съм, че някои от вас са виждали по-висок брой). Нито нула, нито 100 са добри, но „правилното“ число е това, което трябва да разберете, като използвате наличните данни и вашия опит.