Database
 sql >> Baza danych >  >> RDS >> Database

Więcej o CXPACKET Waits:Skośny równoległość

W moim poprzednim poście omówiłem oczekiwania CXPACKET i sposoby zapobiegania lub ograniczania równoległości. Wyjaśniłem również, w jaki sposób wątek kontrolny w operacji równoległej zawsze rejestruje oczekiwanie CXPACKET i że czasami wątki niekontrolujące mogą również rejestrować oczekiwanie CXPACKET. Może się to zdarzyć, jeśli jeden z wątków jest zablokowany w oczekiwaniu na zasób (więc wszystkie inne wątki kończą się przed nim i rejestracja CXPACKET również czeka) lub jeśli oszacowania liczności są nieprawidłowe. W tym poście chciałbym zbadać to drugie.

Gdy oszacowania kardynalności są nieprawidłowe, równoległe wątki wykonujące kwerendę otrzymują nierówną ilość pracy do wykonania. Typowy przypadek to taki, w którym jeden wątek otrzymuje całą pracę lub znacznie więcej pracy niż inne wątki. Oznacza to, że te wątki, które kończą przetwarzanie swoich wierszy (jeśli nawet otrzymały jakiekolwiek) przed najwolniejszym wątkiem, rejestrują CXPACKET od momentu zakończenia do zakończenia najwolniejszego wątku. Ten problem może prowadzić do pozornej eksplozji oczekiwania w CXPACKET i jest powszechnie nazywany wykrzywionym równoległością , ponieważ rozkład pracy między równoległymi wątkami jest przekrzywiony, a nie równy.

Należy zauważyć, że w SQL Server 2016 z dodatkiem SP2 i SQL Server 2017 RTM CU3 wątki konsumenckie nie rejestrują już oczekiwania CXPACKET. Rejestrują oczekiwania CXCONSUMER, które są łagodne i można je zignorować. Ma to na celu zmniejszenie liczby generowanych czekań CXPACKET, a pozostałe z większym prawdopodobieństwem będą wykonalne.

Przykład równoległości skośnej

Przejdę przez wymyślony przykład, aby pokazać, jak zidentyfikować takie przypadki.

Po pierwsze, stworzę scenariusz, w którym tabela zawiera szalenie niedokładne statystyki, ręcznie ustawiając liczbę wierszy i stron w UPDATE STATISTICS oświadczenie (nie rób tego w środowisku produkcyjnym!):

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

Więc moja tabela ma tylko kilka tysięcy wierszy, ale sfałszowałem, że ma 10 milionów wierszy.

Teraz utworzę wymyślone zapytanie, aby wybrać 500 najwyższych wierszy, które będą działać równolegle, ponieważ sądzi, że są miliony wierszy do przeskanowania.

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

I włącz to.

Wyświetlanie czekania CXPACKET

Teraz mogę przyjrzeć się oczekiwaniom CXPACKET, które występują za pomocą prostego skryptu, aby spojrzeć na sys.dm_os_waiting_tasks DMV:

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

Jeśli wykonam to kilka razy, w końcu widzę wyniki pokazujące przekrzywioną równoległość (zdjąłem link do obsługi planu zapytania i ograniczyłem opis zasobu, dla jasności, i zauważyłem, że wstawiłem kod, aby pobrać tekst SQL, jeśli tego chcesz też):

session_id exec_context_id wait_duration_ms wait_type blocking_session_id resource_description 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

Wyniki pokazujące wypaczoną równoległość w działaniu

Wątek kontrolny to ten z exec_context_id ustawione na 0. Inne równoległe wątki to te z exec_context_id wyższa niż 0 i wszystkie pokazują, że CXPACKET czeka poza jednym (zauważ, że exec_context_id = 2 brakuje na liście). Zauważysz, że wszyscy mają własny session_id jako ten, który je blokuje, i to jest poprawne, ponieważ wszystkie wątki czekają na inny wątek z własnego session_id ukończyć. database_id to baza danych, w kontekście której zapytanie jest wykonywane, niekoniecznie baza danych, w której występuje problem, ale zwykle tak jest, chyba że zapytanie używa trzyczęściowego nazewnictwa do wykonania w innej bazie danych.

Wyświetlanie problemu szacowania liczności

Z query_plan kolumny w wynikach zapytania (którą usunąłem dla jasności), możesz kliknąć na nią, aby wyświetlić plan graficzny, a następnie kliknąć prawym przyciskiem myszy i wybrać Widok z Eksploratorem planów SQL Sentry. Pokazuje to jak poniżej:

Od razu widzę, że istnieje problem z oszacowaniem kardynalności, ponieważ rzeczywiste wiersze dla skanowania indeksu klastrowego to tylko 2048, w porównaniu do 10 000 000 wierszy szacunkowych (szacowanych).

Jeśli przewijam, widzę rozkład wierszy w równoległych wątkach, które zostały użyte:

I oto tylko jeden wątek wykonywał jakąkolwiek pracę podczas równoległej części planu – ten, który nie pojawił się w sys.dm_os_waiting_tasks wyjście powyżej.

W takim przypadku poprawka polega na zaktualizowaniu statystyk tabeli.

W moim wymyślonym przykładzie to nie zadziała, ponieważ nie wprowadzono żadnych modyfikacji w tabeli, więc ponownie uruchomię skrypt konfiguracyjny, pomijając UPDATE STATISTICS oświadczenie.

Plan zapytania staje się wtedy:

Tam, gdzie nie ma problemu z kardynalnością ani równoległości – problem rozwiązany!

Podsumowanie

Jeśli widzisz, że CXPACKET czeka, łatwo jest sprawdzić przekrzywioną równoległość, korzystając z metody opisanej powyżej. Wszystkie przypadki, które widziałem, wynikały z tego czy innego rodzaju problemów z szacowaniem kardynalności i często jest to po prostu kwestia aktualizacji statystyk.

Jeśli chodzi o ogólne statystyki oczekiwania, więcej informacji na temat ich używania do rozwiązywania problemów z wydajnością można znaleźć w:

  • Moja seria wpisów na blogu SQLskills, zaczynająca się od statystyk Wait lub proszę powiedz mi, gdzie to boli
  • Moja biblioteka typów Wait Types i Latch Classes tutaj
  • Mój kurs szkoleniowy online Pluralsight SQL Server:Rozwiązywanie problemów z wydajnością za pomocą statystyk oczekiwania
  • Wartość SQL

Miłego rozwiązywania problemów do następnego razu!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Podstawy wyrażeń tablicowych, Część 6 – Rekurencyjne CTE

  2. Typ SCD 6

  3. Pobieranie kompletnych komunikatów o błędach w isql

  4. Polecenia SQL

  5. Jak upuścić tabelę w SQL?