The commands
SET STATISTICS TIME ON
SET STATISTICS IO ON
return information about query executions and are very useful when doing performance tuning work as they inform how long a query took to execute and the amount of IO activity that occurred as a result of that query.
These are very effective features however to my mind they do have a drawback in that the information they provide is not accessible in the actual query window from which the query was executed. This means the results cannot be collected, stored in a table, and then queried – such information would have to be manually copied and pasted from the messages pane into (say) a spreadsheet for further analysis.
This is dumb. I’m a SQL Server developer, I want my data available so that I can bung it into a table in SQL Server and issue queries against it. That is why, a couple of weeks ago, I submitted a request to Microsoft Connect entitled Access to STATS TIME & STATS IO from my query in which I said:
I recently was doing some performance testing work where I was evaluating the affect of changing various settings on a particular query. I would have liked to simply run my query inside a couple of nested loops in order to test all permutations but I could not do that because every time I executed the query I had to pause so I could retrieve the stats returned from STATISTICS IO & STATISTCS TIME and manually copy and paste (yes, copy and paste) the information into a spreadsheet.
This feels pretty dumb in this day and age. Why can we not simply have access to that same information within my query? After all, we have @@ROWCOUNT, ERROR_MESSAGE(), ERROR_NUMBER() etc... that provide very useful information about the previously executed statement, how about @@STATISTICS for returning all the IO & timing info? We can parse the text returned by that function to get all the info we need.
Better still, provide individual functions e.g.:
@@QUERYPARSETIME
@@QUERYCOMPILETIME
@@QUERYEXECUTIONTIME
@@SCANCOUNT
@@LOGICALREADS
@@PHYSICALREADS
@@READAHEADREADS
Ralph Kemperdick noticed my submission and correctly suggested that the same information could be accessed using Extended Events. Based on this I’ve written a script (below) that issues a series of queries against the AdventureWorks2012 sample database, captures similar stats that would be captured by SET STATISTICS then presents them back at the end of the query. Here are those results:
The information is not as comprehensive as what you would get from SET STATISTICS (no Read-Ahead Reads for example, and no breakdown of IO per table) but should be sufficient for most purposes.
You can adapt the script accordingly for whatever information you want to capture, the important part of the script is the creation of the XEvents session for capturing the queries, then reading and shredding the XML results thereafter.
Hope this is useful!
--Create the event session
CREATE EVENT SESSION [queryperf] ON SERVER
ADD EVENT sqlserver.sql_statement_completed
ADD TARGET package0.event_file(SET filename=N'C:\temp\queryperf.xel',max_file_size=(2),max_rollover_files=(100))
WITH ( MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_MULTIPLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=120 SECONDS,MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON);
--Set up some demo queries against AdventureWorks2012 in order to evaluate query time & IO
USE AdventureWorks2012
DECLARE @SalesPersonID INT;
DECLARE @salesTally INT;
DECLARE mycursor CURSOR FOR
SELECT soh.SalesPersonID
FROM Sales.SalesOrderHeader soh
GROUP BY soh.SalesPersonID;
OPEN mycursor;
FETCH NEXT FROM mycursor INTO @SalesPersonID;
ALTER EVENT SESSION [queryperf] ON SERVER STATE = START;
WHILE @@FETCH_STATUS = 0
BEGIN
DBCC FREEPROCCACHE;
DBCC DROPCLEANBUFFERS;
CHECKPOINT;
SELECT @salesTally = COUNT(*)
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.[SalesOrderDetail] sod ON soh.[SalesOrderID] = sod.[SalesOrderID]
WHERE SalesPersonID = @SalesPersonID
FETCH NEXT FROM mycursor INTO @SalesPersonID;
END
CLOSE mycursor;
DEALLOCATE mycursor;
DROP EVENT SESSION [queryperf] ON SERVER;
--Extract query information from the XEvents target
SELECT q.duration,q.cpu_time,q.physical_reads,q.logical_reads,q.writes--,event_data_XML,statement,timestamp
FROM (
SELECT duration=e.event_data_XML.value('(//data[@name="duration"]/value)[1]','int')
, cpu_time=e.event_data_XML.value('(//data[@name="cpu_time"]/value)[1]','int')
, physical_reads=e.event_data_XML.value('(//data[@name="physical_reads"]/value)[1]','int')
, logical_reads=e.event_data_XML.value('(//data[@name="logical_reads"]/value)[1]','int')
, writes=e.event_data_XML.value('(//data[@name="writes"]/value)[1]','int')
, statement=e.event_data_XML.value('(//data[@name="statement"]/value)[1]','nvarchar(max)')
, TIMESTAMP=e.event_data_XML.value('(//@timestamp)[1]','datetime2(7)')
, *
FROM (
SELECT CAST(event_data AS XML) AS event_data_XML
FROM sys.fn_xe_file_target_read_file('C:\temp\queryperf*.xel', NULL, NULL, NULL)
)e
)q
WHERE q.[statement] LIKE 'select @salesTally = count(*)%' --Filters out all the detritus that we're not interested in!
ORDER BY q.[timestamp] ASC
;