1

I was troubleshooting a performance issue and I need an SP to stop getting recompiled because of auto update stats.

I don't want to disable Auto stats update on the entire database so I thought I could disable it on all the tables involved using sp_autostats proc. But even after I disabled auto update stats on all the tables involved, it is still getting recompiled with the reason "Statistics changed".

I checked the last time stats was updated and also used auto_stats extended event to track if the stats were getting updated, but it is not.

REPRO

USE [test]
GO
CREATE TABLE [dbo].[test](
    [a] [int] IDENTITY(1,1) NOT NULL,   [b] [int] NULL, [c] [int] NULL,
PRIMARY KEY CLUSTERED 
(
    [a] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = ON, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [idx] ON [dbo].[test]
(
    [c] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = ON, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE proc [dbo].[sp1] @a int
as
select * from  test  where c=@a  order by b
GO
sp_autostats 'test','off'
go

--creating lots of changes.
--Session 1
set nocount on 
while 1=1
begin
delete top(1) from test
end

--Session 2
set nocount on 
while 1=1
insert into test select 1, FLOOR(RAND() * (10 - 1 + 1)) + 1;

--powershell 3.  Calling the SP in a loop
while ($true) {
    
        $sqlQuery = "EXEC test.dbo.sp1 @a=100;"
        Invoke-Sqlcmd -ServerInstance 'sql1\s14' -Database 'test' -Query $sqlQuery -Encrypt Optional
        Start-Sleep -Milliseconds 500
}

--Monitor sql_statement_recompile extended event.  Should see a recompile event in a couple minutes with "Statistics changed" as recompile_cause
CREATE EVENT SESSION [test] ON SERVER 
ADD EVENT sqlserver.sql_statement_recompile(SET collect_object_name=(1),collect_statement=(1)
    ACTION(sqlserver.server_principal_name,sqlserver.sql_text))
ADD TARGET package0.ring_buffer
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=OFF,STARTUP_STATE=OFF)
GO

I suspect it might have something to do with stats on worktable created for the sort? if we comment out the order by clause in the SP the problem goes away. Periodically calling sp_recompile also seems to reset whatever it is tracking that triggers recompile.

Any workaround for this and anyone else faced this issue?

1 Answer 1

4

The recompile isn't being caused by a change in statistics. The reason code is misleading*.

SQL Server doesn't maintain statistics on sort worktables.

Quoting from Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005:

If a table or an indexed view T has no statistic on it, or none of the existing statistics on T are considered "interesting" during a query compilation, the following threshold-crossing test, based purely on T's cardinality, is still performed:

ABS(card(current) – card(snapshot))) >= RT

card(current) denotes the number of rows in T at present, and card(snapshot) denotes the row count when the query plan was last compiled.

In other words, the statement in the procedure is recompiling because a referenced table has passed the Recompilation Threshold associated with the total number of rows in the table. No individual statistic contributed to the decision.

You can avoid recompilation by adding a KEEPFIXED PLAN query hint to the procedure statement(s).

Forces the Query Optimizer not to recompile a query because of changes in statistics. Specifying KEEPFIXED PLAN makes sure that a query recompiles only if the schema of the underlying tables changes, or if sp_recompile runs against those tables.

With that approach, you can directly target the statement(s) you don't want to recompile. Disabling automatic statistics updates is no longer needed, either via STATISTICS_NORECOMPUTE or sp_autostats.


Repro

This script reproduces the issue on SQL Server 2022 with only a single query window needed.

Setup

DROP PROCEDURE IF EXISTS dbo.sp1;
DROP TABLE IF EXISTS dbo.test;
GO
CREATE TABLE dbo.Test
(
    a integer IDENTITY NOT NULL,
    b integer NULL,
    c integer NULL,

    PRIMARY KEY CLUSTERED (a)
        WITH (STATISTICS_NORECOMPUTE = ON)
);
GO
CREATE NONCLUSTERED INDEX idx
ON dbo.test (c)
WITH (STATISTICS_NORECOMPUTE = ON);
GO
CREATE PROCEDURE dbo.sp1
    @c integer
AS
SELECT 
    T.* 
FROM dbo.test AS T
WHERE 
    T.c = @c
--OPTION (KEEPFIXED PLAN)
;
GO
EXECUTE sys.sp_autostats
    @tblname = N'dbo.test',
    @flagc = 'off'; 

Test

SET NOCOUNT ON;
GO
DBCC TRACEON (205, 3604) 
    WITH NO_INFOMSGS;

EXECUTE dbo.sp1 @c = 1;

INSERT dbo.Test (b, c) VALUES (1, 1);

-- Recompile due to going from zero rows to one
EXECUTE dbo.sp1 @c = 1;

INSERT dbo.Test (b, c)
SELECT
    SV.number,
    SV.number
FROM master.dbo.spt_values AS SV
WHERE
    SV.[type] = N'P'
    AND SV.number BETWEEN 1 AND 500;

-- Recompile due to cardinality change >= 500
EXECUTE dbo.sp1 @c = 1;

INSERT dbo.Test (b, c)
SELECT
    SV.number,
    SV.number
FROM master.dbo.spt_values AS SV
WHERE
    SV.[type] = N'P'
    AND SV.number BETWEEN 1 AND 600;

-- Recompile due to cardinality change >= 500 + 20%
EXECUTE dbo.sp1 @c = 1;

DBCC TRACEOFF (205, 3604) 
    WITH NO_INFOMSGS;
GO
DROP PROCEDURE dbo.sp1;
DROP TABLE dbo.test;

Output

Sample output without the KEEPFIXED PLAN hint:

Data related recompile(card change): Tbl Dbid: 8, Objid: 1652200936, 
current rowcount: 1, compile time rowcount: 0, threshold: 500 (small table)

Data related recompile(card change): Tbl Dbid: 8, Objid: 1652200936, 
current rowcount: 501, compile time rowcount: 1, threshold: 500 (small table)

Data related recompile(card change): Tbl Dbid: 8, Objid: 1652200936, 
current rowcount: 1101, compile time rowcount: 501, threshold: 600 (sublinear heuristic)

With the query hint, there is no output as no recompilations occur.


* There has been a change in statistical information available to the query optimizer, but not a change in a statistics object. It's the closest of the available reasons to choose.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.