我的存储过程每次从Web应用程序调用时都会疯狂地超时。
我启动了Sql Profiler并跟踪了超时的调用,最后找到了这些东西:
除了我的Web应用程序有自己的用户之外,每件事都是相同的(相同的数据库,连接,服务器等) 我还尝试使用Web应用程序的用户直接在工作室中运行查询,并且不会超过6秒。
我如何知道发生了什么?
我假设它与我们使用BLL>的事实无关。 DAL图层或表适配器作为跟踪清楚地显示延迟在实际过程中。这就是我能想到的全部。
编辑我在this link中发现ADO.NET将ARITHABORT
设置为true - 这在大多数时间都有用,但有时会发生这种情况,以及建议的工作-around是将with recompile
选项添加到存储过程。在我的情况下,它不起作用,但我怀疑它与此非常相似。任何人都知道ADO.NET做了什么或者我在哪里可以找到规范?
答案 0 :(得分:74)
我过去曾遇到过类似的问题,所以我很想看到解决这个问题的方法。 Aaron Bertrand对OP的评论导致了Query times out when executed from web, but super-fast when executed from SSMS,虽然这个问题不重复,但答案很可能适用于您的情况。
实质上,听起来SQL Server可能有一个损坏的缓存执行计划。您正在使用您的Web服务器进行糟糕的计划,但SSMS会出现不同的计划,因为ARITHABORT标志上有不同的设置(否则会对您的特定查询/存储过程没有影响)。
有关其他示例,请参阅ADO.NET calling T-SQL Stored Procedure causes a SqlTimeoutException,并提供更完整的解释和解决方案。
答案 1 :(得分:42)
我还体验到,查询在网络上运行缓慢且在SSMS中运行速度很慢,我最终发现这个问题叫做参数嗅探。
我的修复方法是将sproc中使用的所有参数更改为局部变量。
例如。改变:
ALTER PROCEDURE [dbo].[sproc]
@param1 int,
AS
SELECT * FROM Table WHERE ID = @param1
为:
ALTER PROCEDURE [dbo].[sproc]
@param1 int,
AS
DECLARE @param1a int
SET @param1a = @param1
SELECT * FROM Table WHERE ID = @param1a
似乎很奇怪,但它解决了我的问题。
答案 2 :(得分:6)
不要垃圾邮件,但作为其他人希望有用的解决方案,我们的系统会有很高的超时时间。
我尝试使用sp_recompile
设置存储过程以重新编译,这解决了一个SP的问题。
最终有更多的SP超时,其中许多以前从未这样做过,使用DBCC DROPCLEANBUFFERS
和DBBC FREEPROCCACHE
事件的超时率大幅下降 - 有仍然是孤立的事件,有些我怀疑计划重建需要一段时间,有些地方的SP真正表现不佳,需要重新评估。
答案 3 :(得分:4)
可能是在Web应用程序调用SP之前进行的其他一些数据库调用是否保持事务处于打开状态?这可能是此SP在Web应用程序调用时等待的原因。我说在Web应用程序中隔离调用(将其放在新页面上)以确保Web应用程序中的某些先前操作导致此问题。
答案 4 :(得分:1)
只需重新编译存储过程(我的情况下的表函数)对我有用
答案 5 :(得分:1)
经过一些谷歌搜索后,我发现了一个关于这种行为的有趣读物:Slow in the Application, Fast in SSMS?
编译过程时,SQL Server不知道@fromdate的值发生了变化,但在假设@fromdate的值为NULL的情况下编译过程。由于所有与NULL的比较都会产生UNKNOWN,如果@fromdate在运行时仍然具有此值,则查询根本不能返回任何行。如果SQL Server将输入值作为最终事实,它可以构建一个仅具有不能访问该表的常量扫描的计划(运行查询SELECT * FROM Orders WHERE OrderDate> NULL以查看此示例)。但是,无论@fromdate在运行时具有什么值,SQL Server都必须生成一个返回正确结果的计划。另一方面,没有义务制定一个对所有价值观都最好的计划。因此,由于假设不会返回任何行,因此SQL Server将为索引搜索结算。
问题是我的参数可以保留为null,如果它们作为null传递,则会使用默认值进行初始化。
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
我把它改成
create procedure dbo.procedure
@dateTo datetime = null
begin
declare @to datetime = coalesce(@dateTo, getutcdate())
select foo
from dbo.table
where createdDate < @to
end
它再次像魅力一样。
答案 6 :(得分:0)
--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
答案 7 :(得分:0)
您可以通过以下方式定位特定的缓存执行计划:
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%'
然后只删除导致问题的执行计划,例如:
DBCC FREEPROCCACHE (0x050006003FCA862F40A19A93010000000000000000000000)
我现在有一个每 5 分钟运行一次的作业,它会查找运行缓慢的过程或函数,并在发现任何执行计划时自动清除这些执行计划:
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