Som alla veteranproduktions-DBA vet är du ofta under betydande press att diagnostisera och lindra problem med databasprestanda så snabbt som möjligt. Här är tre saker du kanske kan dra nytta av, beroende på din arbetsbelastning och infrastruktur, för att ha en mycket märkbar positiv inverkan på din databasprestanda.
Grundläggande justering av radbutiksindex
De flesta SQL Server-instanser som jag har stött på under min karriär har haft några relativt enkla möjligheter för radbutiksindexjustering. En bra sak med justering av radbutiksindex är att det oftare är under din direkta kontroll som DBA, särskilt jämfört med justering av sökfrågor eller lagrade procedurer, som ofta kontrolleras av utvecklare eller 3-partsleverantörer.
Vissa DBA:er är ovilliga att göra någon indexjustering (särskilt på trepartsdatabaser) eftersom de är oroliga för att gå sönder något eller äventyra leverantörens stöd för databasen eller applikationen. Uppenbarligen måste du vara mer försiktig med trepartsdatabaser och försöka nå ut till leverantören innan du gör några indexändringar själv, men i vissa situationer kanske du inte har något annat genomförbart alternativ (förutom att kasta snabbare hårdvara och lagring på problemet ).
Du kan köra några nyckelfrågor från mina SQL Server Diagnostic Information Queries för att få en bra uppfattning om du kanske har några enkla indexjusteringsmöjligheter på din instans eller databas. Du bör hålla utkik efter saknade indexförfrågningar, saknade indexvarningar, underanvända eller icke-använda icke-klustrade index och möjliga datakomprimeringsmöjligheter.
Det krävs lite erfarenhet, gott omdöme och kunskap om din arbetsbelastning för att göra korrekt indexjustering. Det är alltför vanligt att se människor göra felaktiga indexjusteringar, genom att överlag göra många indexändringar utan att göra den korrekta analysen.
Här är några frågor som jag gillar att använda på databasnivå:
-- Missing Indexes for current database by Index Advantage (Query 1) (Missing Indexes) SELECT DISTINCT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], mid.equality_columns, mid.inequality_columns, mid.included_columns, migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact, OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows] FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) ON migs.group_handle = mig.index_group_handle INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) ON mig.index_handle = mid.index_handle INNER JOIN sys.partitions AS p WITH (NOLOCK) ON p.[object_id] = mid.[object_id] WHERE mid.database_id = DB_ID() AND p.index_id < 2 ORDER BY index_advantage DESC OPTION (RECOMPILE); ------ -- Look at index advantage, last user seek time, number of user seeks to help determine source and importance -- SQL Server is overly eager to add included columns, so beware -- Do not just blindly add indexes that show up from this query!!! -- Find missing index warnings for cached plans in the current database (Query 2) (Missing Index Warnings) -- Note: This query could take some time on a busy instance SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName], cp.objtype, cp.usecounts, cp.size_in_bytes, query_plan FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%' AND dbid = DB_ID() ORDER BY cp.usecounts DESC OPTION (RECOMPILE); ------ -- Helps you connect missing indexes to specific stored procedures or queries -- This can help you decide whether to add them or not -- Possible Bad NC Indexes (writes >= reads) (Query 3) (Bad NC Indexes) SELECT OBJECT_NAME(s.[object_id]) AS [Table Name], i.name AS [Index Name], i.index_id, i.is_disabled, i.is_hypothetical, i.has_filter, i.fill_factor, s.user_updates AS [Total Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference] FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK) INNER JOIN sys.indexes AS i WITH (NOLOCK) ON s.[object_id] = i.[object_id] AND i.index_id = s.index_id WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1 AND s.database_id = DB_ID() AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups) AND i.index_id > 1 AND i.[type_desc] = N'NONCLUSTERED' AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0 ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC OPTION (RECOMPILE); ------ -- Look for indexes with high numbers of writes and zero or very low numbers of reads -- Consider your complete workload, and how long your instance has been running -- Investigate further before dropping an index! -- Breaks down buffers used by current database by object (table, index) in the buffer cache (Query 4) (Buffer Usage) -- Note: This query could take some time on a busy instance SELECT OBJECT_NAME(p.[object_id]) AS [Object Name], p.index_id, CAST(COUNT(*)/128.0 AS DECIMAL(10, 2)) AS [Buffer size(MB)], COUNT(*) AS [BufferCount], p.[Rows] AS [Row Count], p.data_compression_desc AS [Compression Type] FROM sys.allocation_units AS a WITH (NOLOCK) INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK) ON a.allocation_unit_id = b.allocation_unit_id INNER JOIN sys.partitions AS p WITH (NOLOCK) ON a.container_id = p.hobt_id WHERE b.database_id = CONVERT(int, DB_ID()) AND p.[object_id] > 100 AND OBJECT_NAME(p.[object_id]) NOT LIKE N'plan_%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'sys%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'xml_index_nodes%' GROUP BY p.[object_id], p.index_id, p.data_compression_desc, p.[Rows] ORDER BY [BufferCount] DESC OPTION (RECOMPILE); ------ -- Tells you what tables and indexes are using the most memory in the buffer cache -- It can help identify possible candidates for data compression
Använda fördröjd hållbarhet
Funktionen för fördröjd hållbarhet lades till produkten i SQL Server 2014, så den har varit tillgänglig ganska länge. Försenade varaktiga transaktionsåtaganden är asynkrona och rapporterar en transaktionsåtagande som framgångsrikt före loggposterna för transaktionen skrivs faktiskt till lagringsundersystemet. Försenade varaktiga transaktioner blir faktiskt inte varaktiga förrän transaktionsloggposterna spolas till disken.
Den här funktionen är tillgänglig i alla utgåvor av SQL Server. Trots detta ser jag sällan att det används när jag tittar på klientdatabaser. Fördröjd hållbarhet öppnar upp för en viss dataförlust, upp till en hel loggbuffert i värsta fall (som förklaras av Paul Randal här), så det är definitivt inte lämpligt för ett RPO-scenario där absolut ingen dataförlust är acceptabel.
Fördröjd hållbarhet minskar transaktionslatens eftersom den inte väntar på att log IO ska slutföras och återföra kontrollen tillbaka till klienten, och det minskar också låsning och diskkonflikt för samtidiga transaktioner. Dessa två fördelar kan ofta ha en mycket positiv effekt på din fråga och applikationsprestanda med lämplig mycket skrivtung arbetsbelastning.
Fördröjd hållbarhet kommer oftast att hjälpa tunga arbetsbelastningar av OLTP-typ som har mycket frekventa, små skrivtransaktioner där du ser hög skrivlatens på filnivå från sys.dm_io_virtual_file_stats på transaktionsloggfilen och/eller du ser höga WRITELOG-väntningar från sys. dm_os_wait_stats.
Du kan enkelt tvinga SQL Server 2014 eller senare att använda fördröjd hållbarhet för alla transaktioner (utan kodändringar) genom att köra följande kommando:
ALTER DATABASE AdventureWorks2014 SET DELAYED_DURABILITY = FORCED;
Jag har haft klienter som programmässigt slår på och av fördröjd hållbarhet vid olika tidpunkter på dagen (som under schemalagd ETL eller underhållsaktivitet). Jag har också haft kunder som använder fördröjd hållbarhet hela tiden eftersom de har en lämplig arbetsbelastning och risktolerans för dataförlust.
Slutligen har jag haft kunder som aldrig skulle överväga att använda fördröjd hållbarhet, eller helt enkelt inte behöver det med sin arbetsbelastning. Om du misstänker att din arbetsbelastning kan dra nytta av att använda fördröjd hållbarhet, men du är orolig för eventuell dataförlust, då finns det andra alternativ som du kan överväga.
Ett alternativ är funktionen för beständig loggbuffert i SQL Server 2016 SP1, där du kan skapa en andra transaktionsloggfil på 20 MB på en lagringsvolym för direktåtkomstläge (DAX) som finns på en NV-DIMM-beständig minnesenhet. Denna extra transaktionsloggfil används för att cachelagra loggens svans, med åtkomst på bytenivå som kringgår den konventionella lagringsstacken på blocknivå.
Om du tror att din arbetsbelastning kan dra nytta av att använda funktionen för beständig loggbuffert kan du experimentera med att tillfälligt använda fördröjd hållbarhet för att se om det finns en faktisk prestandafördel med din arbetsbelastning innan du spenderar pengarna på det beständiga NV-DIMM-minnet som du skulle behöva använda funktionen för kvarstående loggbuffert.
Flyttar tempdb till Intel Optane DC P4800X Storage
Jag har haft stor framgång med flera nya klienter som flyttade sina tempdb-databasfiler från någon annan typ av lagring till en logisk enhet som backades upp av ett par Intel Optane DC P4800X PCIe NVMe-lagringskort (i en mjukvaru-RAID 1-array).
Dessa lagringskort finns i kapaciteterna 375 GB, 750 GB och 1,5 TB (även om kapaciteten på 1,5 TB är helt ny och fortfarande svår att hitta). De har extremt låg latens (mycket lägre än någon typ av NAND-flashlagring), utmärkt slumpmässig I/O-prestanda vid låga ködjup (mycket bättre än NAND-flashlagring), med konsekventa lässvarstider under en mycket tung skrivbelastning.
De har också högre skrivuthållighet än "skrivintensiv" NAND-flashlagring för företag, och deras prestanda försämras inte eftersom de är nära att vara fulla. Dessa egenskaper gör dessa kort extremt väl lämpade för många tunga tempdb-arbetsbelastningar, särskilt tunga OLTP-arbetsbelastningar och situationer där du använder RCSI i dina användardatabaser (vilket sätter den resulterande versionslagringsbelastningen på tempdb).
Det är också mycket vanligt att se hög skrivfördröjning på tempdb-datafiler från sys.dm_io_virtual_file_stats DMV, så att flytta dina tempdb-datafiler till Optane-lagring är ett sätt att direkt lösa problemet, som kan vara snabbare och enklare än konventionellt arbetsbelastningsinställning.
En annan möjlig användning för Optane-lagringskort är som ett hem för dina transaktionsloggfiler. Du kan också använda Optane-lagring med äldre versioner av SQL Server (så länge som ditt operativsystem och hårdvara stöder det). Det är ett möjligt alternativ till att använda fördröjd hållbarhet (som kräver SQL Server 2014) eller att använda funktionen för beständig loggbuffert (som kräver SQL Server 2016 SP1).
Slutsats
Jag diskuterade tre tekniker för att få en snabb prestationsvinst med SQL Server:
- Konventionell radlagringsindexjustering är tillämplig på alla versioner av SQL Server, och det är ett av de bästa verktygen i din arsenal.
- Fördröjd hållbarhet är tillgänglig i SQL Server 2014 och nyare, och det kan vara mycket fördelaktigt med vissa typer av arbetsbelastning (och RPO-krav). Beständig loggbuffert är tillgänglig i SQL Server 2016 SP1, och den ger liknande fördelar som fördröjd hållbarhet, utan risk för dataförlust.
- Att flytta vissa typer av databasfiler till Intel Optane-lagring kan hjälpa till att lindra prestandaproblem med tempdb eller med användardatabas transaktionsloggfiler. Du kan använda Optane-lagring med äldre versioner av SQL Server, och det krävs ingen kod eller konfigurationsändringar.