Background
I noticed whilst experimenting with running total queries that sometimes the estimated plan just shows a "Fetch Query"
and the actual plan shows repeated Fetches from the Clustered Index Scan
on other occasions (e.g when adding a TOP
to the query) the estimated plan shows a "Population Query" stage that populates a work table
With the actual plan showing a clustered index scan to populate the work table then repeated seeks against that work table.
Question
- What criteria does SQL Server use in choosing one approach over the other?
- Would I be right in thinking that the first method (without the additional work table population step) is more efficient?
(Bonus question: If anyone could explain why each scan in the first query counts as 2 logical reads that might be quite enlightening too)
Additional Information
I have found this article here which explains that FAST_FORWARD
cursors can either use a dynamic plan or a static plan. The first query in this case appears to be using a dynamic plan and the second one a static plan.
I've also found that if I try
SET @C2 = CURSOR DYNAMIC TYPE_WARNING FOR SELECT TOP ...
The cursor gets implicitly converted to a keyset
cursor so it is clear that the TOP
construct is not supported for dynamic cursors, perhaps for the reasons in Ruben's answer - Still looking for a definitive explanation of this.
However I have also read that dynamic cursors tend to be slower than their static counterparts (source 1, source 2) which seems surprising to me given that the static variety have to read the source data, copy it, then read the copy rather than just read the source data. The article I referenced earlier mentions that dynamic cursors use markers
. Can anyone explain what these are? Is it just a RID or the CI key, or something different?
Script
SET STATISTICS IO OFF
CREATE TABLE #T ( ord INT IDENTITY PRIMARY KEY, total INT, Filler char(8000))
INSERT INTO #T (total) VALUES (37),(80),(55),(31),(53)
DECLARE @running_total INT,
@ord INT,
@total INT
SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C1 AS CURSOR;
SET @C1 = CURSOR FAST_FORWARD FOR SELECT ord, total FROM #T ORDER BY ord;
OPEN @C1;
PRINT 'Initial FETCH C1'
FETCH NEXT FROM @C1 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @running_total = @running_total + @total
PRINT 'FETCH C1'
FETCH NEXT FROM @C1 INTO @ord, @total ;
END
SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C2 AS CURSOR;
SET @C2 = CURSOR FAST_FORWARD FOR SELECT TOP 5 ord, total FROM #T ORDER BY ord;
OPEN @C2;
PRINT 'Initial FETCH C2'
FETCH NEXT FROM @C2 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @running_total = @running_total + @total
PRINT 'FETCH C2'
FETCH NEXT FROM @C2 INTO @ord, @total ;
END
PRINT 'End C2'
DROP TABLE #T
top 5
is retrieved in one transaction, it's like a snapshot. Without the worktable, you could get atop 5
containing rows that were never in the table together. – Desert#temp
table so SQL Server could (potentially) recognize that it will be consistent anyway as other transactions can't modify it. I also just triedSET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
and still see the same results. (AndSET ROWCOUNT 5
leaves both plans unchanged too) – Nielson