By: Eli Leiba || Related Tips:More >Performance Tuning
ProblemThe requirement is to get a detailed SQL Server internal query cardinality estimator component plan information report for a given valid query. The information should include call trees, statistics for each operation and applied operation rules available in SQL Server.
SolutionMy solution involves creating a rather simple T-SQL stored procedure in the SQL Server master database, called dbo.usp_GetQueryEstimatorInternalInfo that has one parameter, a valid and tested query string.
The procedure concatenates the query parameter, a complex query hint (OPTION clause) consisting of five different trace flags along with a RECOMPILE hint, creating an extended query (with this hint). The combination of all of these flags ensures that all the detailed, internal information will be printed to the Messages tab in SQL Server Management Studio for further examination.
Here is a description of the trace flags:
Trace Flag Operation 2363 Enable debugging output that shows what statistics objects were used by the cardinality estimator during query compilation in SQL Server 2014. 3604 Redirects trace output to the client so it appears in the SSMS messages tab. 8606 Shows the LogOp_xxx functions call Trees. 8612 Adds extra Info to the trees output. 8619 Shows the applied transformation rules.The procedure dynamically executes the modified query, so all information is output to the Messages tab.
SQL Server Query Estimator -- =================================================================================-- Author: Eli Leiba
-- Create date: 09-2018
-- Procedure Name: dbo.usp_GetQueryEstimatorInternalInfo
-- Description:
-- The procedure gets a string parameter containing a valid query
-- concatenates some query trace flags to the query in order to
-- display the internal cardinality estimator component functions
-- call tree.
-- ==================================================================================
CREATE PROCEDURE dbo.usp_GetQueryEstimatorInternalInfo (@query NVARCHAR (3000))
AS
BEGIN
DECLARE @TSQL NVARCHAR (4000) = ''
SET NOCOUNT ON
SET @TSQL = CONCAT (
@query,
' option(recompile, ',
' querytraceon 2363, ',
' querytraceon 3604, ',
' querytraceon 8606, ',
' querytraceon 8612, ',
' querytraceon 8619) '
)
EXEC (@TSQL);
SET NOCOUNT OFF
END
GO
Here is an example query we will use with the procedure:
SELECT p.productid, p.ProductName, c.CategoryNameFROM Northwind.dbo.products p, Northwind.dbo.Categories c
WHERE p.categoryid = c.CategoryID AND p.categoryid < 4
The procedure call looks like this. Note you have to specify the database in the FROM clause for each object.
exec master.dbo.usp_GetQueryEstimatorInternalInfo@query='SELECT p.productid, p.ProductName, c.CategoryName
FROM Northwind.dbo.products p, Northwind.dbo.Categories c
WHERE p.categoryid = c.CategoryID and p.categoryid < 4'
GO
The results on my test machine are as follows. This is output to the Messages tab.
*** Input Tree: ***LogOp_Project QCOL: .ProductID QCOL: .ProductName QCOL: [c].CategoryName [ Card=0 ]
LogOp_Select [ Card=0 ]
LogOp_Join [ Card=0 ]
LogOp_Get TBL: Northwind.dbo.products(alias TBL: p) Northwind.dbo.products TableID=533576939 TableReferenceID=0 IsRow: COL: IsBaseRow1000 [ Card=0 ]
LogOp_Get TBL: Northwind.dbo.categories(alias TBL: c) Northwind.dbo.categories TableID=309576141 TableReferenceID=0 IsRow: COL: IsBaseRow1001 [ Card=0 ]
ScaOp_Const TI(bit,ML=1) XVAR(bit,Not Owned,Value=1)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: .CategoryID
ScaOp_Identifier QCOL: [c].CategoryID
ScaOp_Comp x_cmpLt
ScaOp_Identifier QCOL: .CategoryID
ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=4)
AncOp_PrjList
*******************
*** Simplified Tree: ***
LogOp_Join [ Card=0 ]
LogOp_Select [ Card=0 ]
LogOp_Get TBL: Northwind.dbo.products(alias TBL: p) Northwind.dbo.products TableID=533576939 TableReferenceID=0 IsRow: COL: IsBaseRow1000 [ Card=0 ]
ScaOp_Comp x_cmpLt
ScaOp_Identifier QCOL: .CategoryID
ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=4)
LogOp_Select [ Card=0 ]
LogOp_Get TBL: Northwind.dbo.categories(alias TBL: c) Northwind.dbo.categories TableID=309576141 TableReferenceID=0 IsRow: COL: IsBaseRow1001 [ Card=0 ]
ScaOp_Comp x_cmpLt
ScaOp_Identifier QCOL: [c].CategoryID
ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=4)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [c].CategoryID
ScaOp_Identifier QCOL: .CategoryID
*******************
Begin selectivity computation
Input tree:
LogOp_Select
CStCollBaseTable(ID=1, CARD=77 TBL: Northwind.dbo.products AS TBL: p)
ScaOp_Comp x_cmpLt
ScaOp_Identifier QCOL: .CategoryID
ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=4)
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: .CategoryID
Loaded histogram for column QCOL: .CategoryID from stats with id 2
Selectivity: 0.480519
Stats collection generated:
CStCollFilter(ID=3, CARD=37)
CStCollBaseTable(ID=1, CARD=77 TBL: Northwind.dbo.products AS TBL: p)
End selectivity computation
Begin selectivity computation
Input tree:
LogOp_Select
CStCollBaseTable(ID=2, CARD=8 TBL: Northwind.dbo.categories AS TBL: c)
ScaOp_Comp x_cmpLt
ScaOp_Identifier QCOL: [c].CategoryID
ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=4)
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [c].CategoryID
Loaded histogram for column QCOL: [c].CategoryID from stats with id 1
Selectivity: 0.375
Stats collection generated:
CStCollFilter(ID=4, CARD=3)
CStCollBaseTable(ID=2, CARD=8 TBL: Northwind.dbo.categories AS TBL: c)
End selectivity computation
Begin selectivity computation
Input tree:
LogOp_Join
CStCollFilter(ID=3, CARD=37)
CStCollBaseTable(ID=1, CARD=77 TBL: Northwind.dbo.products AS TBL: p)
CStCollFilter(ID=4, CARD=3)
CStCollBaseTable(ID=2, CARD=8 TBL: Northwind.dbo.categories AS TBL: c)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [c].CategoryID
ScaOp_Identifier QCOL: .CategoryID
Plan for computation:
CSelCalcExpressionComparedToExpression( QCOL: .CategoryID x_cmpEq QCOL: [c].CategoryID )
Selectivity: 0.333333
Stats collection generated:
CStCollJoin(ID=5, CARD=37 x_jtInner)
CStCollFilter(ID=3, CARD=37)
CStCollBaseTable(ID=1, CARD=77 TBL: Northwind.dbo.products AS TBL: p)
CStCollFilter(ID=4, CARD=3)
CStCollBaseTable(ID=2, CARD=8 TBL: Northwind.dbo.categories AS TBL: c)
End selectivity computation
*** Join-collapsed Tree: ***
LogOp_Join [ Card=37 ]
LogOp_Select [ Card=37 ]
LogOp_Get TBL: Northwind.dbo.products(ali