I'm reviewing a stored procedure that's been a performance problem. And I just realized that some of the tables which are used in the stored procedure don't even show up AT ALL in the results from Set Statistics I/O?
Is this normal, or a bug? It's a little disturbing because I use the results of Set Statistics I/O a lot, when figuring out what to index.
The tables that don't show up in the output of Set Statistics I/O are in a subselect on the main query, like the ActivityFeedCache table below:
SELECT *
FROM (
SELECT AF.ActivityFeedID
,A.ActivityType AS ActivityType
,A.IconClass
,AF.SubscriptionID
,AF.ObjectID
,AF.ObjectType
,(
SELECT L.Locale
,AFC.LocalizedMessage
FROM ActivityFeedCache AFC
JOIN Locales L
ON AFC.LocaleID = L.LocaleID
WHERE ActivityFeedID = AF.ActivityFeedID
FOR JSON PATH
) AS MessageJSON
FROM ActivityFeed AF
ETC
ETC
) AS Results
This is SQL Server 2017 (RTM-CU13).
EDIT:
I've narrowed down to a small query that shows a missing table in the Statistics IO output. The Locales table is missing in Statistics I/O, but shows up in the execution plan. Here it is:
-- Locales table doesn't show in statistics i/o output
SELECT AF.ActivityFeedID
,(
SELECT L.Locale
,AFC.LocalizedTeamMessage AS LocalizedMessage
FROM ActivityFeedTeamCache AFC
JOIN Locales L
ON AFC.LocaleID = L.LocaleID
WHERE ActivityFeedID = AF.ActivityFeedID
FOR JSON PATH
) AS TeamMessageJSON
FROM ActivityFeed AF
Where AF.ActivityFeedID = (select top 1 ActivityFeedID from ActivityFeed)
Here's the Statistics I/O output:
(1 row affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ActivityFeedTeamCache'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ActivityFeed'. Scan count 1, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
And here's a small chunk of the actual execution plan XML, just so you can see the Locales table is actually being used:
<IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
<DefinedValues>
<DefinedValue>
<ColumnReference Database="[Database1]" Schema="[dbo]" Table="[Locales]" Alias="[L]" Column="Locale" />
</DefinedValue>
</DefinedValues>
<Object Database="[Database1]" Schema="[dbo]" Table="[Locales]" Index="[PK_Locales]" Alias="[L]" IndexKind="Clustered" Storage="RowStore" />
<SeekPredicates>
<SeekPredicateNew>
<SeekKeys>
<Prefix ScanType="EQ">
<RangeColumns>
<ColumnReference Database="[Database1]" Schema="[dbo]" Table="[Locales]" Alias="[L]" Column="LocaleID" />
</RangeColumns>
<RangeExpressions>
<ScalarOperator ScalarString="[Database1].[dbo].[ActivityFeedTeamCache].[LocaleID] as [AFC].[LocaleID]">
Best Answer
There is a scenario where an estimated execution plan can contain an operator that doesn't get used at runtime, for example when a predicate renders the operator a no-op.
It's tricky to spot because there aren't many clues it's happened, other than the operator's
actual execution count
is 0. I helped drive a change in SentryOne Plan Explorer that makes it more obvious - the operator icon gets dimmed. You can read about this change in this blog post under the heading, "Apply opacity for operators with 0 executions + actual rows."If a table is only touched by a single operator in the execution plan, and that operator gets eliminated at runtime (meaning the table does not get touched), that table will not show up in
Statistics IO
.