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

Korzystanie ze śledzenia przyczynowości w celu zrozumienia wykonywania zapytania

Kiedy jesteś głęboko zaangażowany w rozwiązywanie problemów w programie SQL Server, czasami chcesz poznać dokładną kolejność wykonywania zapytań. Widzę to w przypadku bardziej skomplikowanych procedur składowanych, które zawierają warstwy logiki, lub w scenariuszach, w których jest dużo nadmiarowego kodu. Zdarzenia rozszerzone są tutaj naturalnym wyborem, ponieważ są zwykle używane do przechwytywania informacji o wykonywaniu zapytań. Często możesz użyć identyfikatora sesji i znacznika czasu, aby zrozumieć kolejność zdarzeń, ale jest opcja sesji dla XE, która jest jeszcze bardziej niezawodna:Śledź przyczynowość.

Gdy włączysz śledzenie przyczynowości dla sesji, do każdego zdarzenia zostanie dodany identyfikator GUID i numer sekwencyjny, których można następnie użyć do prześledzenia kolejności występowania zdarzeń. Koszty ogólne są minimalne i mogą być znaczną oszczędnością czasu w wielu scenariuszach rozwiązywania problemów.

Konfiguracja

Korzystając z bazy danych WideWorldImporters, utworzymy procedurę składowaną do użycia:

DROP PROCEDURE IF EXISTS [Sales].[usp_CustomerTransactionInfo];
GO
 
CREATE PROCEDURE [Sales].[usp_CustomerTransactionInfo]
	@CustomerID INT
AS	
 
	SELECT [CustomerID], SUM([AmountExcludingTax])
	FROM [Sales].[CustomerTransactions]
	WHERE [CustomerID] = @CustomerID
	GROUP BY [CustomerID];
 
	SELECT COUNT([OrderID])
	FROM [Sales].[Orders]
	WHERE [CustomerID] = @CustomerID
GO

Następnie utworzymy sesję wydarzenia:

CREATE EVENT SESSION [TrackQueries] ON SERVER 
ADD EVENT sqlserver.sp_statement_completed(
    WHERE ([sqlserver].[is_system]=(0))),
ADD EVENT sqlserver.sql_statement_completed(
    WHERE ([sqlserver].[is_system]=(0)))
ADD TARGET package0.event_file
(
  SET filename=N'C:\temp\TrackQueries',max_file_size=(256)
)
WITH
(
  MAX_MEMORY = 4096 KB, 
  EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
  MAX_DISPATCH_LATENCY = 30 SECONDS,
  MAX_EVENT_SIZE = 0 KB,
  MEMORY_PARTITION_MODE = NONE,
  TRACK_CAUSALITY = ON,
  STARTUP_STATE = OFF
);

Zamierzamy również uruchamiać zapytania ad hoc, więc przechwytujemy zarówno sp_statement_completed (wypowiedzi ukończone w ramach procedury składowanej), jak i sql_statement_completed (ukończone instrukcje, które nie znajdują się w procedurze składowanej). Zauważ, że opcja TRACK_CAUSALITY dla sesji jest ustawiona na ON. Ponownie, to ustawienie jest specyficzne dla sesji zdarzenia i musi być włączone przed jej uruchomieniem. Nie możesz włączyć tego ustawienia w locie, na przykład możesz dodawać lub usuwać zdarzenia i cele podczas trwania sesji.

Aby rozpocząć sesję wydarzenia za pomocą interfejsu użytkownika, kliknij go prawym przyciskiem myszy i wybierz opcję Rozpocznij sesję.

Testowanie

W Management Studio uruchomimy następujący kod:

EXEC [Sales].[usp_CustomerTransactionInfo] 490;
 
SELECT [c].[CustomerID], [c].[CustomerName], [p].[FullName], [o].[OrderID]
    FROM [Application].[People] [p]
    JOIN [Sales].[Customers] [c] ON [p].[PersonID] = [c].[PrimaryContactPersonID]
    JOIN [Sales].[Orders] [o] ON [c].[CustomerID] = [o].[CustomerID]
    WHERE [p].[FullName] = 'Naseem Radan';

Oto nasze wyjście XE:

Zauważ, że pierwsze wykonane zapytanie, które jest podświetlone, to SELECT @@SPID i ma identyfikator GUID FDCCB1CF-CA55-48AA-8FBA-7F5EBF870674. Nie wykonaliśmy tego zapytania, wystąpiło ono w tle i chociaż sesja XE jest skonfigurowana do odfiltrowywania zapytań systemowych, to – z jakiegokolwiek powodu – jest nadal przechwytywane.

Następne cztery wiersze reprezentują kod, który faktycznie wykonaliśmy. Istnieją dwa zapytania z procedury składowanej, sama procedura składowana, a następnie nasze zapytanie ad hoc. Wszystkie mają ten sam GUID, ACBFFD99-2400-4AFF-A33F-351821667B24. Obok identyfikatora GUID znajduje się identyfikator sekwencji (seq), a zapytania są ponumerowane od jednego do czterech.

W naszym przykładzie nie użyliśmy GO do rozdzielenia instrukcji na różne partie. Zwróć uwagę, jak zmienia się wyjście, gdy to robimy:

EXEC [Sales].[usp_CustomerTransactionInfo] 490;
GO
 
SELECT [c].[CustomerID], [c].[CustomerName], [p].[FullName], [o].[OrderID]
    FROM [Application].[People] [p]
    JOIN [Sales].[Customers] [c] ON [p].[PersonID] = [c].[PrimaryContactPersonID]
    JOIN [Sales].[Orders] [o] ON [c].[CustomerID] = [o].[CustomerID]
    WHERE [p].[FullName] = 'Naseem Radan';
GO

Nadal mamy taką samą liczbę wierszy, ale teraz mamy trzy identyfikatory GUID. Jeden dla SELECT @@SPID (E8D136B8-092F-439D-84D6-D4EF794AE753), jeden dla trzech zapytań reprezentujących procedurę składowaną (F962B9A4-0665-4802-9E6C-B217634D8787) i jeden dla zapytania ad hoc (5DD6A5FE -9702-4DE5-8467-8D7CF55B5D80).

To najprawdopodobniej zobaczysz, patrząc na dane z aplikacji, ale zależy to od tego, jak aplikacja działa. Jeśli korzysta z puli połączeń, a połączenia są regularnie resetowane (co jest oczekiwane), każde połączenie będzie miało swój własny identyfikator GUID.
Możesz go odtworzyć, korzystając z przykładowego kodu PowerShell poniżej:

while(1 -eq 1)
{
 
    $SqlConn = New-Object System.Data.SqlClient.SqlConnection;
    $SqlConn.ConnectionString = "Data Source=Hedwig\SQL2017;Initial Catalog=WideWorldImporters;Integrated Security=True;Application Name = MyCoolApp";
    $SQLConn.Open()
    $SqlCmd = $SqlConn.CreateCommand();
 
    $SqlCmd.CommandText = "SELECT TOP 1 CustomerID FROM Sales.Customers ORDER BY NEWID();"
    $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
    $SqlReader = $SqlCmd.ExecuteReader();
    $Results = New-Object System.Collections.ArrayList;
 
    while ($SqlReader.Read())
    {
	    $Results.Add($SqlReader.GetSqlInt32(0)) | Out-Null;
    }
 
    $SqlReader.Close();
 
 
	$Value = Get-Random -InputObject $Results;
 
    $SqlCmd = $SqlConn.CreateCommand();
	$SqlCmd.CommandText = "Sales.usp_CustomerTransactionInfo"
	$SqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure;
 
	$SqlParameter = $SqlCmd.Parameters.AddWithValue("@CustomerID", $Value);
	$SqlParameter.SqlDbType = [System.Data.SqlDbType]::Int;
 
	$SqlCmd.ExecuteNonQuery();
 
    $SqlConn.Close();
 
    $Names = New-Object System.Collections.Generic.List``1[System.String]
 
    $SqlConn = New-Object System.Data.SqlClient.SqlConnection
    $SqlConn.ConnectionString = "Data Source=Hedwig\SQL2017;Initial Catalog=WideWorldImporters;User Id=aw_webuser;Password=12345;Application Name=AdventureWorks Online Ordering;Workstation ID=AWWEB01";
    $SqlConn.Open();
 
    $SqlCmd = $SqlConn.CreateCommand();
    $SqlCmd.CommandText = "SELECT FullName FROM Application.People ORDER BY NEWID();";
    $dr = $SqlCmd.ExecuteReader();
 
    while($dr.Read())
    {
          $Names.Add($dr.GetString(0));
    }
 
    $SqlConn.Close();
 
    $name = Get-Random -InputObject $Names;
 
    $query = [String]::Format("SELECT [c].[CustomerID], [c].[CustomerName], [p].[FullName], [o].[OrderID]
    FROM [Application].[People] [p]
    JOIN [Sales].[Customers] [c] ON [p].[PersonID] = [c].[PrimaryContactPersonID]
    JOIN [Sales].[Orders] [o] ON [c].[CustomerID] = [o].[CustomerID]
    WHERE [p].[FullName] = '{0}';", $name);
 
    $SqlConn = New-Object System.Data.SqlClient.SqlConnection
    $SqlConn.ConnectionString = "Data Source=Hedwig\SQL2017;Initial Catalog=WideWorldImporters;User Id=aw_webuser;Password=12345;Application Name=AdventureWorks Online Ordering;Workstation ID=AWWEB01";
    $SqlConn.Open();
 
    $SqlCmd = $sqlconnection.CreateCommand();
    $SqlCmd.CommandText = $query 
 
    $SqlCmd.ExecuteNonQuery();
 
    $SqlConn.Close();
}

Oto przykład wyjścia zdarzeń rozszerzonych po krótkim czasie działania kodu:

Istnieją cztery różne identyfikatory GUID dla naszych pięciu instrukcji, a jeśli spojrzysz na powyższy kod, zobaczysz, że wykonano cztery różne połączenia. Jeśli zmienisz sesję zdarzenia tak, aby zawierała zdarzenie rpc_completed, możesz zobaczyć wpisy z exec sp_reset_connection.
Twoje dane wyjściowe XE będą zależeć od kodu i aplikacji; Wspomniałem na początku, że jest to przydatne przy rozwiązywaniu problemów z bardziej złożonymi procedurami składowanymi. Rozważ następujący przykład:

DROP PROCEDURE IF EXISTS [Sales].[usp_CustomerTransactionInfo];
GO
 
CREATE PROCEDURE [Sales].[usp_CustomerTransactionInfo]
	@CustomerID INT
AS	
 
	SELECT [CustomerID], SUM([AmountExcludingTax])
	FROM [Sales].[CustomerTransactions]
	WHERE [CustomerID] = @CustomerID
	GROUP BY [CustomerID];
 
	SELECT COUNT([OrderID])
	FROM [Sales].[Orders]
	WHERE [CustomerID] = @CustomerID
 
GO
 
DROP PROCEDURE IF EXISTS [Sales].[usp_GetFullCustomerInfo];
GO
 
CREATE PROCEDURE [Sales].[usp_GetFullCustomerInfo]
	@CustomerID INT
AS	
 
	SELECT 
		[o].[CustomerID], 
		[o].[OrderDate], 
		[ol].[StockItemID], 
		[ol].[Quantity],
		[ol].[UnitPrice]
	FROM [Sales].[Orders] [o]
	JOIN [Sales].[OrderLines] [ol] 
		ON [o].[OrderID] = [ol].[OrderID]
	WHERE [o].[CustomerID] = @CustomerID
	ORDER BY [o].[OrderDate] DESC;
 
	SELECT
		[o].[CustomerID], 
		SUM([ol].[Quantity]*[ol].[UnitPrice])
	FROM [Sales].[Orders] [o]
	JOIN [Sales].[OrderLines] [ol] 
		ON [o].[OrderID] = [ol].[OrderID]
	WHERE [o].[CustomerID] = @CustomerID
	GROUP BY [o].[CustomerID]
	ORDER BY [o].[CustomerID] ASC;
GO
 
DROP PROCEDURE IF EXISTS [Sales].[usp_GetCustomerData];
GO
 
CREATE PROCEDURE [Sales].[usp_GetCustomerData]
	@CustomerID INT
AS
 
BEGIN
 
	SELECT *
	FROM [Sales].[Customers]
 
	EXEC [Sales].[usp_CustomerTransactionInfo] @CustomerID
 
	EXEC [Sales].[usp_GetFullCustomerInfo] @CustomerID
 
END
GO

Tutaj mamy dwie procedury składowane, usp_TransctionInfo i usp_GetFullCustomerInfo, które są wywoływane przez inną procedurę składowaną, usp_GetCustomerData. Często zdarza się to, a nawet dodatkowe poziomy zagnieżdżenia z procedurami składowanymi. Jeśli wykonamy usp_GetCustomerData z Management Studio, zobaczymy:

EXEC [Sales].[usp_GetCustomerData] 981;

Tutaj wszystkie wykonania wystąpiły na tym samym identyfikatorze GUID, BF54CD8F-08AF-4694-A718-D0C47DBB9593, i możemy zobaczyć kolejność wykonywania zapytania od jednego do ośmiu przy użyciu kolumny seq. W przypadkach, w których wywoływanych jest wiele procedur składowanych, często zdarza się, że wartość identyfikatora sekwencji sięga setek lub tysięcy.

Wreszcie, w przypadku, gdy patrzysz na wykonanie zapytania i uwzględniłeś przyczynę śledzenia, i znajdujesz zapytanie, które ma niską skuteczność – ponieważ posortowałeś dane wyjściowe na podstawie czasu trwania lub jakiegoś innego wskaźnika, pamiętaj, że możesz znaleźć inne zapytania grupując według GUID:

Dane wyjściowe zostały posortowane według czasu trwania (najwyższa wartość zakreślona na czerwono) i dodałem je do zakładek (na fioletowo) za pomocą przycisku Przełącz zakładkę. Jeśli chcemy zobaczyć inne zapytania dotyczące identyfikatora GUID, pogrupuj według identyfikatora GUID (kliknij prawym przyciskiem myszy nazwę kolumny u góry, a następnie wybierz opcję Grupuj według tej kolumny), a następnie użyj przycisku Następna zakładka, aby wrócić do naszego zapytania:

Teraz możemy zobaczyć wszystkie zapytania, które zostały wykonane w ramach tego samego połączenia i w wykonanej kolejności.

Wniosek

Opcja Śledź przyczynowość może być niezwykle przydatna podczas rozwiązywania problemów z wydajnością zapytań i próby zrozumienia kolejności zdarzeń w SQL Server. Jest to również przydatne podczas konfigurowania sesji zdarzenia, która korzysta z celu pair_matching, aby upewnić się, że dopasowujesz właściwe pole/działanie i znajdujesz właściwe niedopasowane zdarzenie. Ponownie, jest to ustawienie na poziomie sesji, więc należy je zastosować przed rozpoczęciem sesji wydarzenia. W przypadku trwającej sesji zatrzymaj sesję zdarzeń, włącz opcję, a następnie uruchom ją ponownie.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Samouczek DBMS:Kompletny kurs Crash na DBMS

  2. Praca z danymi Java w Qlik Sense

  3. Błąd podczas wysyłania pakietu QUERY

  4. Tap and Park:model danych aplikacji parkingowej

  5. Mity dotyczące wydajności:Obcięcie nie może zostać wycofane