Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Sử dụng theo dõi nhân quả để hiểu thực thi truy vấn

Khi tham gia sâu vào việc khắc phục sự cố trong SQL Server, đôi khi bạn thấy mình muốn biết chính xác thứ tự mà các truy vấn đã chạy. Tôi thấy điều này với các thủ tục được lưu trữ phức tạp hơn có chứa các lớp logic hoặc trong các tình huống có nhiều mã dư thừa. Sự kiện mở rộng là một lựa chọn tự nhiên ở đây, vì nó thường được sử dụng để thu thập thông tin về việc thực thi truy vấn. Bạn thường có thể sử dụng session_id và dấu thời gian để hiểu thứ tự của các sự kiện, nhưng có một tùy chọn phiên cho XE thậm chí còn đáng tin cậy hơn:Theo dõi Nhân quả.

Khi bạn bật Theo dõi nhân quả cho một phiên, nó sẽ thêm GUID và số thứ tự cho mọi sự kiện, sau đó bạn có thể sử dụng để xem xét thứ tự các sự kiện đã xảy ra. Tổng chi phí là tối thiểu và nó có thể tiết kiệm thời gian đáng kể trong nhiều trường hợp khắc phục sự cố.

Thiết lập

Sử dụng cơ sở dữ liệu WideWorldImporters, chúng tôi sẽ tạo một quy trình được lưu trữ để sử dụng:

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

Sau đó, chúng tôi sẽ tạo một phiên sự kiện:

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

Chúng tôi cũng sẽ chạy các truy vấn đặc biệt, vì vậy chúng tôi đang nắm bắt cả sp_statement_completed (các câu lệnh được hoàn thành trong một thủ tục được lưu trữ) và sql_statement_completed (các câu lệnh đã hoàn thành không nằm trong một thủ tục được lưu trữ). Lưu ý rằng tùy chọn TRACK_CAUSALITY cho phiên được đặt thành BẬT. Một lần nữa, cài đặt này dành riêng cho phiên sự kiện và phải được bật trước khi bắt đầu. Bạn không thể bật cài đặt một cách nhanh chóng, giống như bạn có thể thêm hoặc xóa các sự kiện và mục tiêu trong khi phiên đang chạy.

Để bắt đầu phiên sự kiện thông qua giao diện người dùng, chỉ cần nhấp chuột phải vào nó và chọn Bắt đầu phiên.

Thử nghiệm

Trong Management Studio, chúng tôi sẽ chạy đoạn mã sau:

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

Đây là kết quả XE của chúng tôi:

Lưu ý rằng truy vấn đầu tiên được thực thi, được đánh dấu, là SELECT @@ SPID và có GUID là FDCCB1CF-CA55-48AA-8FBA-7F5EBF870674. Chúng tôi đã không thực hiện truy vấn này, nó xảy ra trong nền và mặc dù phiên XE được thiết lập để lọc ra các truy vấn hệ thống, truy vấn này - vì bất kỳ lý do gì - vẫn được ghi lại.

Bốn dòng tiếp theo đại diện cho mã mà chúng tôi thực sự đã chạy. Có hai truy vấn từ thủ tục được lưu trữ, chính thủ tục được lưu trữ và sau đó là truy vấn đặc biệt của chúng tôi. Tất cả đều có cùng một GUID, ACBFFD99-2400-4AFF-A33F-351821667B24. Bên cạnh GUID là id trình tự (seq) và các truy vấn được đánh số từ một đến bốn.

Trong ví dụ của chúng tôi, chúng tôi đã không sử dụng GO để tách các câu lệnh thành các lô khác nhau. Lưu ý kết quả thay đổi như thế nào khi chúng ta làm điều đó:

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

Chúng ta vẫn có tổng số hàng bằng nhau, nhưng bây giờ chúng ta có ba GUID. Một cho SELECT @@ SPID (E8D136B8-092F-439D-84D6-D4EF794AE753), một cho ba truy vấn đại diện cho thủ tục được lưu trữ (F962B9A4-0665-4802-9E6C-B217634D8787) và một cho truy vấn đặc biệt (5DD6A5FE -9702-4DE5-8467-8D7CF55B5D80).

Đây là những gì bạn rất có thể sẽ thấy khi xem dữ liệu từ ứng dụng của mình, nhưng nó phụ thuộc vào cách ứng dụng hoạt động. Nếu nó sử dụng gộp kết nối và các kết nối được đặt lại thường xuyên (dự kiến), thì mỗi kết nối sẽ có GUID riêng.
Bạn có thể tạo lại điều này bằng cách sử dụng mã PowerShell mẫu bên dưới:

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();
}

Dưới đây là một ví dụ về đầu ra các sự kiện mở rộng sau khi cho phép mã chạy một chút:

Có bốn GUID khác nhau cho năm câu lệnh của chúng tôi và nếu bạn nhìn vào đoạn mã ở trên, bạn sẽ thấy có bốn kết nối khác nhau được thực hiện. Nếu bạn thay đổi phiên sự kiện để bao gồm sự kiện rpc_completed, bạn có thể thấy các mục nhập có thực thi sp_reset_connection.
Đầu ra XE của bạn sẽ phụ thuộc vào mã và ứng dụng của bạn; Tôi đã đề cập ban đầu rằng điều này hữu ích khi khắc phục sự cố các thủ tục được lưu trữ phức tạp hơn. Hãy xem xét ví dụ sau:

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

Ở đây chúng ta có hai thủ tục được lưu trữ, usp_TransctionInfo và usp_GetFullCustomerInfo, được gọi bằng một thủ tục được lưu trữ khác, usp_GetCustomerData. Không có gì lạ khi thấy điều này hoặc thậm chí thấy các mức lồng ghép bổ sung với các thủ tục được lưu trữ. Nếu chúng tôi thực thi usp_GetCustomerData từ Management Studio, chúng tôi sẽ thấy như sau:

EXEC [Sales].[usp_GetCustomerData] 981;

Ở đây, tất cả các lần thực thi xảy ra trên cùng một GUID, BF54CD8F-08AF-4694-A718-D0C47DBB9593 và chúng ta có thể thấy thứ tự thực hiện truy vấn từ một đến tám bằng cách sử dụng cột seq. Trong trường hợp có nhiều thủ tục được lưu trữ được gọi, việc giá trị id trình tự lên đến hàng trăm hoặc hàng nghìn không có gì lạ.

Cuối cùng, trong trường hợp bạn đang xem xét việc thực thi truy vấn và bạn đã bao gồm Theo dõi Nhân quả và bạn thấy một truy vấn hoạt động kém - vì bạn đã sắp xếp đầu ra dựa trên thời lượng hoặc một số chỉ số khác, hãy lưu ý rằng bạn có thể tìm thấy kết quả khác truy vấn bằng cách nhóm trên GUID:

Đầu ra đã được sắp xếp theo thời lượng (giá trị cao nhất được khoanh đỏ) và tôi đã đánh dấu đầu ra (màu tím) bằng cách sử dụng nút Toggle Bookmark. Nếu chúng ta muốn xem các truy vấn khác cho GUID, hãy nhóm theo GUID (Nhấp chuột phải vào tên cột ở trên cùng, sau đó chọn Nhóm theo Cột này), rồi sử dụng nút Dấu trang Tiếp theo để quay lại truy vấn của chúng tôi:

Bây giờ chúng ta có thể thấy tất cả các truy vấn được thực thi trong cùng một kết nối và theo thứ tự đã thực thi.

Kết luận

Tùy chọn Theo dõi Nhân quả có thể vô cùng hữu ích khi khắc phục sự cố hiệu suất truy vấn và cố gắng hiểu thứ tự của các sự kiện trong SQL Server. Nó cũng hữu ích khi thiết lập một phiên sự kiện sử dụng mục tiêu pair_matching, để giúp đảm bảo bạn đang đối sánh trên đúng trường / hành động và tìm ra sự kiện chưa khớp phù hợp. Một lần nữa, đây là cài đặt cấp phiên, vì vậy nó phải được áp dụng trước khi bạn bắt đầu phiên sự kiện. Đối với một phiên đang chạy, hãy dừng phiên sự kiện, bật tùy chọn, sau đó bắt đầu lại.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách các kế hoạch song song bắt đầu - Phần 3

  2. Sử dụng các Hàm T-SQL DATEADD, DATEDIFF và DATEPART trong các thuật ngữ đơn giản

  3. THÔNG TIN CHỨNG TỪ KHÓA NGOẠI LỆ SQL:Hướng dẫn Cơ bản, Dễ dàng cho Người mới

  4. Toán tử SQL nhỏ hơn () cho người mới bắt đầu

  5. SQL Injection là gì?