Lös sista sidans inläggskonkurrens för PAGELATCH_EX i SQL-servern
Ursprunglig produktversion: SQL Server
Ursprungligt KB-nummer: 4460004
Den här artikeln beskriver hur du löser infogningskonkurnitet PAGELATCH_EX
på sista sidan i SQL Server.
Symptom
Föreställ dig följande scenarier:
Du har en kolumn som innehåller sekventiella värden, till exempel en identitetskolumn eller en DateTime-kolumn som infogas via funktionen Getdate().
Du har ett grupperat index som har den sekventiella kolumnen som en inledande kolumn.
Kommentar
Det vanligaste scenariot är en klustrad primärnyckel i en identitetskolumn. Mindre ofta kan det här problemet observeras för icke-illustrerade index.
Programmet utför frekventa INSERT- eller UPDATE-åtgärder mot tabellen.
Du har många processorer i systemet. Vanligtvis har servern 16 processorer eller mer. Med den här maskinvarukonfigurationen kan flera sessioner utföra INSERT-åtgärderna mot samma tabell samtidigt.
I den här situationen kan det uppstå en minskning av programmets prestanda. När du undersöker väntetyper i sys.dm_exec_requests
ser du väntetider på PAGELATCH_EX väntetyp och många sessioner som väntar på den här väntetypen.
Ett annat problem uppstår om du kör följande diagnostikfråga i systemet:
välj session_id, wait_type, wait_time, wait_resource varifrån sys.dm_exec_requests
session_id > 50 och wait_type = "pagelatch_ex"
I den här situationen kan du få resultat som liknar följande.
session_id | wait_type | wait_time | wait_resource |
---|---|---|---|
60 | PAGELATCH_EX | 100 | 5:1:4144 |
75 | PAGELATCH_EX | 123 | 5:1:4144 |
79 | PAGELATCH_EX | 401 | 5:1:4144 |
80 | PAGELATCH_EX | 253 | 5:1:4144 |
81 | PAGELATCH_EX | 312 | 5:1:4144 |
82 | PAGELATCH_EX | 355 | 5:1:4144 |
84 | PAGELATCH_EX | 312 | 5:1:4144 |
85 | PAGELATCH_EX | 338 | 5:1:4144 |
87 | PAGELATCH_EX | 405 | 5:1:4144 |
88 | PAGELATCH_EX | 111 | 5:1:4144 |
90 | PAGELATCH_EX | 38 | 5:1:4144 |
92 | PAGELATCH_EX | 115 | 5:1:4144 |
94 | PAGELATCH_EX | 49 | 5:1:4144 |
101 | PAGELATCH_EX | 301 | 5:1:4144 |
102 | PAGELATCH_EX | 45 | 5:1:4144 |
103 | PAGELATCH_EX | 515 | 5:1:4144 |
105 | PAGELATCH_EX | 39 | 5:1:4144 |
Du märker att flera sessioner väntar på samma resurs som liknar följande mönster:
database_id = 5, file_id = 1, databas page_id = 4144
Kommentar
Database_id ska vara en användardatabas (ID-numret är större än eller lika med 5). Om database_id är 2 kan du i stället uppleva problemet som beskrivs i Filer, spårningsflaggor och uppdateringar på TEMPDB.
Orsak
PAGELATCH (låser på en data- eller indexsida) är en trådsynkroniseringsmekanism. Den används för att synkronisera en kortvarig fysisk åtkomst till databassidor som finns i Buffer-cacheminnet.
PAGELATCH skiljer sig från en PAGEIOLATCH. Den senare används för att synkronisera fysisk åtkomst till sidor när de läses från eller sparas till en hårddisk.
Pagelatches är vanliga i varje system, eftersom de säkerställer fysiskt sidoskydd. Ett klustrat index sammanställer datan genom den ledande nyckelkolumnen. Av denna anledning, när du skapar indexet på en sekventiell kolumn, sker alla nya datainlägg på samma sida vid slutet av indexet tills den sidan är fylld. Men under hög belastning kan det samtidiga INLÄGGET eventuellt orsaka en konkurrens på sista sidan av B-tre. Denna konkurrens kan inträffa på klustrade och icke-klustrade index. Orsaken är att icke-klustrade index sammanställer sidor på bladnivå genom huvudnyckeln. Detta problem är även känt som inläggskonkurrens på sista sidan.
Mer information finns i Diagnostisera och lösa spärrkonkurration på SQL Server.
Åtgärd
Du kan välja något av följande två alternativ för att lösa problemet.
Alternativ 1: Kör stegen direkt i en notebook-fil via Azure Data Studio
Kommentar
Innan du försöker öppna den här notebook-filen kontrollerar du att Azure Data Studio är installerat på den lokala datorn. Om du vill installera det går du till Lär dig hur du installerar Azure Data Studio.
Alternativ 2: Följ stegen manuellt
För att lösa den här konkurrensen är den övergripande strategin att förhindra att alla samtidiga INSERT-åtgärder kommer åt samma databassida. Gör i stället varje INSERT-åtgärd till en annan sida och öka samtidigheten. Därför uppnår någon av följande metoder som organiserar data efter en annan kolumn än den sekventiella kolumnen det här målet.
1. Bekräfta konkurrensen om PAGELATCH_EX och identifiera konkurrensresursen
Det här T-SQL-skriptet hjälper dig att identifiera om det finns PAGELATCH_EX
väntetider i systemet med flera sessioner (5 eller mer) med betydande väntetid (10 ms eller mer). Det hjälper dig också att identifiera vilket objekt och index som konkurrensen är på med hjälp av sys.dm_exec_requests och DBCC PAGE eller sys.fn_PageResCracker och sys.dm_db_page_info (endast SQL Server 2019).
SET NOCOUNT ON
DECLARE @dbname SYSNAME, @dbid INT, @objectid INT, @indexid INT, @indexname SYSNAME, @sql VARCHAR(8000), @manul_identification VARCHAR(8000)
IF (CONVERT(INT, SERVERPROPERTY('ProductMajorVersion')) >= 15)
BEGIN
DROP TABLE IF EXISTS #PageLatchEXContention
SELECT DB_NAME(page_info.database_id) DbName, r.db_id DbId, page_info.[object_id] ObjectId, page_info.index_id IndexId
INTO #PageLatchEXContention
FROM sys.dm_exec_requests AS er
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS st
CROSS APPLY sys.fn_PageResCracker (er.page_resource) AS r
CROSS APPLY sys.dm_db_page_info(r.[db_id], r.[file_id], r.page_id, 'DETAILED') AS page_info
WHERE er.wait_type = 'PAGELATCH_EX' AND page_info.database_id not in (db_id('master'),db_id('msdb'), db_id('model'), db_id('tempdb'))
GROUP BY DB_NAME(page_info.database_id), r.db_id, page_info.[object_id], page_info.index_id
HAVING COUNT(er.session_id) > 5 AND Max (er.wait_time) > 10
SELECT * FROM #PageLatchEXContention
IF EXISTS (SELECT 1 FROM #PageLatchEXContention)
BEGIN
DECLARE optimize_for_seq_key_cursor CURSOR FOR
SELECT DbName, DbId, ObjectId, IndexId FROM #PageLatchEXContention
OPEN optimize_for_seq_key_cursor
FETCH NEXT FROM optimize_for_seq_key_cursor into @dbname, @dbid, @objectid , @indexid
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT 'Consider using below statement to enable OPTIMIZE_FOR_SEQUENTIAL_KEY for the indexes in the "' + @dbname + '" database' AS Recommendation
SELECT @sql = 'select ''use ' + @dbname + '; ALTER INDEX '' + i.name + '' ON ' + OBJECT_NAME(@objectid, @dbid) + ' SET (OPTIMIZE_FOR_SEQUENTIAL_KEY = ON )'' AS Corrective_Action from #PageLatchEXContention pl JOIN ' + @dbname+'.sys.indexes i ON pl.ObjectID = i.object_id WHERE object_id = ' + CONVERT(VARCHAR, @objectid) + ' AND index_id = ' + CONVERT(VARCHAR, @indexid)
EXECUTE (@sql)
FETCH NEXT FROM optimize_for_seq_key_cursor INTO @dbname, @dbid, @objectid , @indexid
END
CLOSE optimize_for_seq_key_cursor
DEALLOCATE optimize_for_seq_key_cursor
END
ELSE
SELECT 'No PAGELATCH_EX contention found on user databases on in SQL Server at this time'
END
ELSE
BEGIN
IF OBJECT_ID('tempdb..#PageLatchEXContentionLegacy') IS NOT NULL
DROP TABLE #PageLatchEXContentionLegacy
SELECT 'dbcc traceon (3604); dbcc page(' + replace(wait_resource,':',',') + ',3); dbcc traceoff (3604)' TSQL_Command
INTO #PageLatchEXContentionLegacy
FROM sys.dm_exec_requests er
WHERE er.wait_type = 'PAGELATCH_EX' AND er.database_id NOT IN (db_id('master'),db_id('msdb'), db_id('model'), db_id('tempdb'))
GROUP BY wait_resource
HAVING COUNT(er.session_id) > 5 AND Max (er.wait_time) > 10
SELECT * FROM #PageLatchEXContentionLegacy
IF EXISTS(SELECT 1 FROM #PageLatchEXContentionLegacy)
BEGIN
SELECT 'On SQL Server 2017 or lower versions, you can manually identify the object where contention is occurring using DBCC PAGE locate the m_objId = ??. Then SELECT OBJECT_NAME(object_id_identified) and locate indexes with sequential values in this object' AS Recommendation
DECLARE get_command CURSOR FOR
SELECT TSQL_Command from #PageLatchEXContentionLegacy
OPEN get_command
FETCH NEXT FROM get_command into @sql
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @sql AS Step1_Run_This_Command_To_Find_Object
SELECT 'select OBJECT_NAME(object_id_identified)' AS Step2_Find_Object_Name_From_ID
FETCH NEXT FROM get_command INTO @sql
END
CLOSE get_command
DEALLOCATE get_command
SELECT 'Follow https://learn.microsoft.com/troubleshoot/sql/performance/resolve-pagelatch-ex-contention for resolution recommendations that fits your environment best' Step3_Apply_KB_article
END
ELSE
SELECT 'No PAGELATCH_EX contention found on user databases on in SQL Server at this time'
END
2. Välj en metod för att lösa problemet
Du kan använda någon av följande metoder för att lösa problemet. Välj den som passar bäst för dina omständigheter.
Metod 1: Använd alternativet OPTIMIZE_FOR_SEQUENTIAL_KEY index (endast SQL Server 2019)
I SQL Server 2019 lades ett nytt indexalternativ (OPTIMIZE_FOR_SEQUENTIAL_KEY
) till som kan hjälpa dig att lösa problemet utan att använda någon av följande metoder. Mer information finns i Bakom kulisserna på OPTIMIZE_FOR_SEQUENTIAL_KEY .
Metod 2: Flytta primärnyckel från identitetskolumnen
Gör kolumnen som innehåller sekventiella värden till ett icke-grupperat index och flytta sedan det klustrade indexet till en annan kolumn. För en primärnyckel i en identitetskolumn tar du till exempel bort den klustrade primärnyckeln och återskapar den som en icke-illustrerad primärnyckel. Den här metoden är den enklaste metoden och uppnår målet direkt.
Anta till exempel att du har följande tabell som definierades med hjälp av en klustrad primärnyckel i en identitetskolumn.
USE testdb;
CREATE TABLE Customers
( CustomerID BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
CustomerLastName VARCHAR (32) NOT NULL,
CustomerFirstName VARCHAR(32) NOT NULL );
Om du vill ändra den här designen kan du ta bort primärnyckelindexet och omdefiniera det.
USE testdb;
ALTER TABLE Customers
DROP CONSTRAINT PK__Customer__A4AE64B98819CFF6;
ALTER TABLE Customers
ADD CONSTRAINT pk_Cust1
PRIMARY KEY NONCLUSTERED (CustomerID)
Metod 3: Gör den inledande nyckeln till en icke-sekventiell kolumn
Ändra ordning på den klustrade indexdefinitionen på ett sådant sätt att den inledande kolumnen inte är den sekventiella kolumnen. Den här metoden kräver att det klustrade indexet är ett sammansatt index. I en kundtabell kan du till exempel göra en CustomerLastName-kolumn till den inledande kolumnen följt av CustomerID. Vi rekommenderar att du testar den här metoden noggrant för att se till att den uppfyller prestandakraven.
USE testdb;
ALTER TABLE Customers
ADD CONSTRAINT pk_Cust1
PRIMARY KEY CLUSTERED (CustomerLastName, CustomerID)
Metod 4: Lägg till ett icke-sekventiellt värde som en inledande nyckel
Lägg till ett icke-sekventiellt hashvärde som den ledande indexnyckeln. Den här tekniken hjälper också till att sprida ut skären. Ett hash-värde genereras som en modulo som matchar antalet processorer i systemet. I ett 16-CPU-system kan du till exempel använda modulo 16. Den här metoden sprider ut INSERT-åtgärderna jämnt mot flera databassidor.
USE testdb;
CREATE TABLE Customers
( CustomerID BIGINT IDENTITY(1,1) NOT NULL,
CustomerLastName VARCHAR (32) NOT NULL,
CustomerFirstName VARCHAR(32) NOT NULL );
ALTER TABLE Customers
ADD [HashValue] AS (CONVERT([TINYINT], abs([CustomerID])%16)) PERSISTED NOT NULL;
ALTER TABLE Customers
ADD CONSTRAINT pk_table1
PRIMARY KEY CLUSTERED (HashValue, CustomerID);
Metod 5: Använd ett GUID som en inledande nyckel
Använd ett GUID som den inledande nyckelkolumnen i ett index för att säkerställa en enhetlig fördelning av infogningar.
Kommentar
Även om målet uppnås rekommenderar vi inte den här metoden eftersom den medför flera utmaningar, inklusive en stor indexnyckel, frekventa siddelningar, låg sidtäthet och så vidare.
Metod 6: Använd tabellpartitionering och en beräknad kolumn med ett hash-värde
Använd tabellpartitionering och en beräknad kolumn som har ett hash-värde för att sprida ut INSERT-åtgärderna. Eftersom den här metoden använder tabellpartitionering kan den endast användas i Enterprise-utgåvor av SQL Server.
Kommentar
Du kan använda partitionerade tabeller i SQL Server 2016 SP1 Standard Edition. Mer information finns i beskrivningen av "Tabell- och indexpartitionering" i artikeln Utgåvor och funktioner som stöds i SQL Server 2016.
Följande är ett exempel i ett system som har 16 processorer.
USE testdb;
CREATE TABLE Customers
( CustomerID BIGINT IDENTITY(1,1) NOT NULL,
CustomerLastName VARCHAR (32) NOT NULL,
CustomerFirstName VARCHAR(32) NOT NULL );
ALTER TABLE Customers
ADD [HashID] AS CONVERT(TINYINT, ABS(CustomerID % 16)) PERSISTED NOT NULL;
CREATE PARTITION FUNCTION pf_hash (TINYINT) AS RANGE LEFT FOR VALUES (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) ;
CREATE PARTITION SCHEME ps_hash AS PARTITION pf_hash ALL TO ([PRIMARY]);
CREATE UNIQUE CLUSTERED INDEX CIX_Hash
ON Customers (CustomerID, HashID) ON ps_hash(HashID);
Metod 7: Växla till Minnesintern OLTP
Alternativt kan du använda Minnesintern OLTP, särskilt om spärrkonkurrationen är hög. Den här tekniken eliminerar spärrkonkurrationen överlag. Du måste dock designa om och migrera de specifika tabellerna, där sidspärrkonkurvans observeras, till en minnesoptimerad tabell. Du kan använda rapporten Minnesoptimeringsrådgivare och Analys av transaktionsprestanda för att avgöra om migrering är möjlig och vad du kan göra för migreringen. Om du vill ha mer information om hur minnesintern OLTP eliminerar spärrkonkurration kan du ladda ned och granska dokumentet i Minnesintern OLTP – Vanliga arbetsbelastningsmönster och migreringsöverväganden.