Stored procedure slow when called from web, fast from Management Studio

asp.netSql Server-2008Stored Procedures

asp.net Problem Overview


I have stored procedure that insanely times out every single time it's called from the web application.

I fired up the Sql Profiler and traced the calls that time out and finally found out these things:

  1. When executed the statements from within the MS SQL Management Studio, with same arguments (in fact, I copied the procedure call from sql profile trace and ran it): It finishes in 5~6 seconds avg.
  2. But when called from web application, it takes in excess of 30 seconds (in trace) so my webpage actually times out by then.

Apart from the fact that my web application has its own user, every thing is same (same database, connection, server etc) I also tried running the query directly in the studio with the web application's user and it doesn't take more than 6 sec.

How do I find out what is happening?

I am assuming it has nothing to do with the fact that we use BLL > DAL layers or Table adapters as the trace clearly shows the delay is in the actual procedure. That is all I can think of.

EDIT I found out in this link that ADO.NET sets ARITHABORT to true - which is good for most of the time but sometime this happens, and the suggested work-around is to add with recompile option to the stored proc. In my case, it's not working but I suspect it's something very similar to this. Anyone knows what else ADO.NET does or where I can find the spec?

asp.net Solutions


Solution 1 - asp.net

I've had a similar issue arise in the past, so I'm eager to see a resolution to this question. Aaron Bertrand's comment on the OP led to https://stackoverflow.com/questions/2248112/sql-server-query-times-out-when-executed-from-web-but-super-fast-when-executed/, and while the question is not a duplicate, the answer may very well apply to your situation.

In essence, it sounds like SQL Server may have a corrupt cached execution plan. You're hitting the bad plan with your web server, but SSMS lands on a different plan since there is a different setting on the ARITHABORT flag (which would otherwise have no impact on your particular query/stored proc).

See https://stackoverflow.com/questions/834124/ado-net-calling-t-sql-stored-procedure-causes-a-sqltimeoutexception/839055#839055 for another example, with a more complete explanation and resolution.

Solution 2 - asp.net

I also experience that queries were running slowly from the web and fast in SSMS and I eventually found out that the problem was something called parameter sniffing.

The fix for me was to change all the parameters that are used in the sproc to local variables.

eg. change:

ALTER PROCEDURE [dbo].[sproc] 
	@param1 int,
AS
SELECT * FROM Table WHERE ID = @param1 

to:

ALTER PROCEDURE [dbo].[sproc] 
	@param1 int,
AS
DECLARE @param1a int
SET @param1a = @param1
SELECT * FROM Table WHERE ID = @param1a

Seems strange, but it fixed my problem.

Solution 3 - asp.net

Not to spam, but as a hopefully helpful solution for others, our system saw a high degree of timeouts.

I tried setting the stored procedure to be recompiled by using sp_recompile and this resolved the issue for the one SP.

Ultimately there were a larger number of SP's that were timing-out, many of which had never done so before, by using DBCC DROPCLEANBUFFERS and DBCC FREEPROCCACHE the incident rate of timeouts has plummeted significantly - there are still isolated occurrences, some where I suspect the plan regeneration is taking a while, and some where the SPs are genuinely under-performant and need re-evaluation.

Solution 4 - asp.net

Could it be that some other DB calls made before the web application calls the SP is keeping a transaction open? That could be a reason for this SP to wait when called by the web application. I say isolate the call in the web application (put it on a new page) to ensure that some prior action in the web application is causing this issue.

Solution 5 - asp.net

Simply recompiling the stored procedure (table function in my case) worked for me

Solution 6 - asp.net

like @Zane said it could be due to parameter sniffing. I experienced the same behaviour and I took a look at the execution plan of the procedure and all the statements of the sp in a row (copied all the statements form the procedure, declared the parameters as variables and asigned the same values for the variable as the parameters had). However the execution plan looked completely different. The sp execution took 3-4 seconds and the statements in a row with the exact same values was instantly returned.

executionplan

After some googling I found an interesting read about that behaviour: [Slow in the Application, Fast in SSMS?][2]

> When compiling the procedure, SQL Server does not know that the value of @fromdate changes, but compiles the procedure under the assumption that @fromdate has the value NULL. Since all comparisons with NULL yield UNKNOWN, the query cannot return any rows at all, if @fromdate still has this value at run-time. If SQL Server would take the input value as the final truth, it could construct a plan with only a Constant Scan that does not access the table at all (run the query SELECT * FROM Orders WHERE OrderDate > NULL to see an example of this). But SQL Server must generate a plan which returns the correct result no matter what value @fromdate has at run-time. On the other hand, there is no obligation to build a plan which is the best for all values. Thus, since the assumption is that no rows will be returned, SQL Server settles for the Index Seek.

The problem was that I had parameters which could be left null and if they were passed as null the would be initialised with a default value.

create procedure dbo.procedure
    @dateTo	datetime = null
begin
    if (@dateTo is null)
    begin
	    select @dateTo  = GETUTCDATE()
    end

    select foo
    from dbo.table
    where createdDate < @dateTo
end

After I changed it to

create procedure dbo.procedure
    @dateTo	datetime = null
begin
    declare @to datetime = coalesce(@dateTo, getutcdate())
    
    select foo
    from dbo.table
    where createdDate < @to
end 

it worked like a charm again.

[2]: http://www.sommarskog.se/query-plan-mysteries.html "Slow in the Application, Fast in SSMS?"

Solution 7 - asp.net

You can target specific cached execution plans via:

SELECT cp.plan_handle, st.[text]
FROM sys.dm_exec_cached_plans AS cp 
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st
WHERE [text] LIKE N'%your troublesome SP or function name etc%'

And then remove only the execution plans causing issues via, for example:

DBCC FREEPROCCACHE (0x050006003FCA862F40A19A93010000000000000000000000)

I've now got a job running every 5 minutes that looks for slow running procedures or functions and automatically clears down those execution plans if it finds any:

if exists (
	SELECT cpu_time, *
	FROM sys.dm_exec_requests req
	CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
	--order by req.total_elapsed_time desc
	WHERE ([text] LIKE N'%your troublesome SP or function name etc%')
	and cpu_time > 8000
)
begin

	SELECT cp.plan_handle, st.[text]
	into #results
	FROM sys.dm_exec_cached_plans AS cp 
	CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st
	WHERE [text] LIKE N'%your troublesome SP or function name etc%'
	delete #results where text like 'SELECT cp.plan_handle%'
	--select * from #results

	declare @handle varbinary(max)
	declare @handleconverted varchar(max)
	declare @sql varchar(1000)

	DECLARE db_cursor CURSOR FOR  
	select plan_handle from #results

	OPEN db_cursor   
	FETCH NEXT FROM db_cursor INTO @handle

	WHILE @@FETCH_STATUS = 0   
	BEGIN   

		--e.g. DBCC FREEPROCCACHE (0x050006003FCA862F40A19A93010000000000000000000000)
		print @handle
		set @handleconverted = '0x' + CAST('' AS XML).value('xs:hexBinary(sql:variable("@handle"))', 'VARCHAR(MAX)')
		print @handleconverted
		set @sql = 'DBCC FREEPROCCACHE (' + @handleconverted + ')'
		print 'DELETING: ' + @sql
		EXEC(@sql)

		FETCH NEXT FROM db_cursor INTO @handle
	END   

	CLOSE db_cursor   
	DEALLOCATE db_cursor

	drop table #results

end

Solution 8 - asp.net

--BEFORE
CREATE PROCEDURE [dbo].[SP_DEMO]
( 
    @ToUserId bigint=null
 )
AS
BEGIN
SELECT * FROM tbl_Logins WHERE LoginId = @ToUserId 
END
--AFTER CHANGING TO IT WORKING FINE
CREATE PROCEDURE [dbo].[SP_DEMO]
( 
    @ToUserId bigint=null
 )
AS
BEGIN
DECLARE @Toid bigint=null
SET @Toid=@ToUserId
SELECT * FROM tbl_Logins WHERE LoginId = @Toid 
END

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioniamseriousView Question on Stackoverflow
Solution 1 - asp.netStriplingWarriorView Answer on Stackoverflow
Solution 2 - asp.netZaneView Answer on Stackoverflow
Solution 3 - asp.netClintView Answer on Stackoverflow
Solution 4 - asp.netTundeyView Answer on Stackoverflow
Solution 5 - asp.netGuy BiberView Answer on Stackoverflow
Solution 6 - asp.netTomPezView Answer on Stackoverflow
Solution 7 - asp.netTDPView Answer on Stackoverflow
Solution 8 - asp.netCodeView Answer on Stackoverflow