Database
 sql >> Database >  >> RDS >> Database

Altro su CXPACKET Waits:skewed parallelism

Nel mio post precedente, ho discusso delle attese di CXPACKET e dei modi per prevenire o limitare il parallelismo. Ho anche spiegato come il thread di controllo in un'operazione parallela registra sempre un'attesa CXPACKET e che a volte i thread non di controllo possono anche registrare le attese CXPACKET. Ciò può accadere se uno dei thread è bloccato in attesa di una risorsa (quindi tutti gli altri thread terminano prima di essa e registrano anche CXPACKET in attesa) o se le stime di cardinalità non sono corrette. In questo post vorrei esplorare quest'ultimo.

Quando le stime di cardinalità non sono corrette, ai thread paralleli che eseguono il lavoro di query viene assegnata una quantità di lavoro irregolare. Il caso tipico è quando a un thread viene assegnato tutto il lavoro o molto più lavoro rispetto agli altri thread. Ciò significa che quei thread che terminano l'elaborazione delle loro righe (se ne sono state fornite) prima del thread più lento registrano un CXPACKET dal momento in cui terminano fino al termine del thread più lento. Questo problema può portare a un'apparente esplosione nelle attese di CXPACKET ed è comunemente chiamato parallalismo distorto , perché la distribuzione del lavoro tra i fili paralleli è obliqua, non uniforme.

Si noti che in SQL Server 2016 SP2 e SQL Server 2017 RTM CU3, i thread consumer non registrano più le attese di CXPACKET. Registrano le attese di CXCONSUMER, che sono benigne e possono essere ignorate. Questo serve a ridurre il numero di attese CXPACKET generate e le restanti hanno maggiori probabilità di essere attuabili.

Esempio di parallelismo distorto

Illustrerò un esempio inventato per mostrare come identificare tali casi.

Prima di tutto, creerò uno scenario in cui una tabella ha statistiche estremamente imprecise, impostando manualmente il numero di righe e pagine in un UPDATE STATISTICS dichiarazione (non farlo in produzione!):

USE [master];
GO
 
IF DB_ID (N'ExecutionMemory') IS NOT NULL
BEGIN
    ALTER DATABASE [ExecutionMemory] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [ExecutionMemory];
END
GO
 
CREATE DATABASE [ExecutionMemory];
GO
USE [ExecutionMemory];
GO
 
CREATE TABLE dbo.[Test] (
    [RowID] INT IDENTITY,
    [ParentID] INT,
    [CurrentValue] NVARCHAR (100),
    CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([RowID]));
GO
 
INSERT INTO dbo.[Test] ([ParentID], [CurrentValue])
SELECT 
    CASE WHEN ([t1].[number] % 3 = 0)
        THEN [t1].[number] – [t1].[number] % 6
        ELSE [t1].[number] END, 
    'Test' + CAST ([t1].[number] % 2 AS VARCHAR(11))
FROM [master].[dbo].[spt_values] AS [t1]
WHERE [t1].[type] = 'P';
GO
 
UPDATE STATISTICS dbo.[Test] ([PK_Test]) WITH ROWCOUNT = 10000000, PAGECOUNT = 1000000;
GO

Quindi la mia tabella contiene solo poche migliaia di righe, ma ho simulato che avesse 10 milioni di righe.

Ora creerò una query artificiale per selezionare le prime 500 righe, che andranno in parallelo poiché ritiene che ci siano milioni di righe da scansionare.

USE [ExecutionMemory];
GO
 
SET NOCOUNT ON;
GO
 
DECLARE @CurrentValue NVARCHAR (100);
 
WHILE (1=1)
SELECT TOP (500) 
    @CurrentValue = [CurrentValue]
FROM dbo.[Test]
ORDER BY NEWID() DESC;
GO

E impostalo in esecuzione.

Visualizzazione delle attese di CXPACKET

Ora posso esaminare le attese di CXPACKET che si verificano utilizzando un semplice script per esaminare sys.dm_os_waiting_tasks Motorizzazione:

SELECT
    [owt].[session_id],
    [owt].[exec_context_id],
    [owt].[wait_duration_ms],
    [owt].[wait_type],
    [owt].[blocking_session_id],
    [owt].[resource_description],
    [er].[database_id],
    [eqp].[query_plan]
FROM sys.dm_os_waiting_tasks [owt]
INNER JOIN sys.dm_exec_sessions [es] ON
    [owt].[session_id] = [es].[session_id]
INNER JOIN sys.dm_exec_requests [er] ON
    [es].[session_id] = [er].[session_id]
OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est]
OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp]
WHERE
    [es].[is_user_process] = 1
ORDER BY
    [owt].[session_id],
    [owt].[exec_context_id];

Se lo eseguo alcune volte, alla fine vedo alcuni risultati che mostrano un parallelismo distorto (ho rimosso il collegamento all'handle del piano di query e ridotto la descrizione della risorsa, per chiarezza, e ho notato che ho inserito il codice per afferrare il testo SQL se lo desideri anche):

id_sessione exec_context_id wait_duration_ms wait_type blocking_session_id descrizione_risorsa database_id
56 0 1 CXPACKET NULL exchangeEvent 13
56 1 1 CXPACKET 56 exchangeEvent 13
56 3 1 CXPACKET 56 exchangeEvent 13
56 4 1 CXPACKET 56 exchangeEvent 13
56 5 1 CXPACKET 56 exchangeEvent 13
56 6 1 CXPACKET 56 exchangeEvent 13
56 7 1 CXPACKET 56 exchangeEvent 13

Risultati che mostrano un parallelismo distorto in azione

Il thread di controllo è quello con exec_context_id impostato su 0. Gli altri thread paralleli sono quelli con exec_context_id maggiore di 0 e mostrano tutte le attese CXPACKET a parte una (nota che exec_context_id = 2 manca dall'elenco). Noterai che tutti elencano il proprio id_sessione come quello che li sta bloccando, ed è corretto perché tutti i thread sono in attesa di un altro thread dal proprio id_sessione completare. Il id_database è il database nel cui contesto viene eseguita la query, non necessariamente il database in cui si trova il problema, ma di solito lo è a meno che la query non utilizzi una denominazione in tre parti per essere eseguita in un database diverso.

Visualizzazione del problema relativo alla stima della cardinalità

Con il query_plan colonna nell'output della query (che ho rimosso per chiarezza), è possibile fare clic su di essa per visualizzare il piano grafico, quindi fare clic con il pulsante destro del mouse e selezionare Visualizza con SQL Sentry Plan Explorer. Questo mostra come di seguito:

Vedo immediatamente che c'è un problema di stima della cardinalità, poiché le righe effettive per la scansione dell'indice cluster sono solo 2.048, rispetto a 10.000.000 di righe (stimate).

Se scorro, posso vedere la distribuzione delle righe sui thread paralleli utilizzati:

Lo ed ecco, solo un singolo thread stava svolgendo alcun lavoro durante la parte parallela del piano, quello che non veniva visualizzato in sys.dm_os_waiting_tasks uscita sopra.

In questo caso, la soluzione consiste nell'aggiornare le statistiche per la tabella.

Nel mio esempio inventato non funzionerà, poiché non ci sono state modifiche alla tabella, quindi eseguirò nuovamente lo script di configurazione, tralasciando UPDATE STATISTICS dichiarazione.

Il piano di query diventa quindi:

Dove non ci sono problemi di cardinalità e nemmeno di parallelismo:problema risolto!

Riepilogo

Se vedi che si verificano attese di CXPACKET, è facile verificare il parallelismo distorto, usando il metodo sopra descritto. Tutti i casi che ho visto sono stati dovuti a problemi di stima della cardinalità di un tipo o dell'altro, e spesso si tratta semplicemente di aggiornare le statistiche.

Per quanto riguarda le statistiche generali sull'attesa, puoi trovare ulteriori informazioni sull'utilizzo per la risoluzione dei problemi relativi alle prestazioni in:

  • Le mie serie di post sul blog SQLskills, a partire dalle statistiche di attesa, o per favore dimmi dove fa male
  • La mia libreria Tipi di attesa e classi Latch qui
  • Corso di formazione online My Pluralsight SQL Server:risoluzione dei problemi relativi alle prestazioni utilizzando le statistiche di attesa
  • Sentiera SQL

Alla prossima volta, buona risoluzione dei problemi!