问题
In my SQL Server 2008 R2 database, I have a query which calls a function.
Lately this query has started to execute very slowly. I found that the user-defined function is hampering all the query.
I tried to execute this function alone and it took 40 seconds to finish, while previously it executed in 3-4 seconds. So I tried to execute code that is inside of function and executed in this 3-4 seconds.
I cannot understand why executing code of function takes far fewer time than calling function itself. I tried all this in SSMS only.
That's the function itself
ALTER FUNCTION [dbo].[fn_SI_GetMark] (@AREA_ID int, @BD datetime, @ED datetime)
RETURNS decimal(24, 2) AS
BEGIN
Declare @mon int;
set @Mon = 1;
if(DateDiff(day, @BD, @ED) > 40)
begin
declare @q1sb datetime; set @q1sb = Convert(datetime, '01.01.'+Convert(nvarchar(4), Year(@BD)));
declare @q1eb datetime; set @q1eb = Convert(datetime, '01.04.'+Convert(nvarchar(4), Year(@BD)));
declare @q2sb datetime; set @q2sb = Convert(datetime, '01.04.'+Convert(nvarchar(4), Year(@BD)));
declare @q2eb datetime; set @q2eb = Convert(datetime, '01.07.'+Convert(nvarchar(4), Year(@BD)));
declare @q3sb datetime; set @q3sb = Convert(datetime, '01.07.'+Convert(nvarchar(4), Year(@BD)));
declare @q3eb datetime; set @q3eb = Convert(datetime, '01.10.'+Convert(nvarchar(4), Year(@BD)));
declare @q4sb datetime; set @q4sb = Convert(datetime, '01.10.'+Convert(nvarchar(4), Year(@BD)));
declare @q4eb datetime; set @q4eb = Convert(datetime, '01.01.'+(Convert(nvarchar(4), Year(@BD) + 1)));
if((@BD >= @q1sb) and (@BD < @q1eb))
begin
set @BD = @q1sb;
end
else if((@BD >= @q2sb) and (@BD < @q2eb))
begin
set @BD = @q2sb;
end
else if((@BD >= @q3sb) and (@BD < @q3eb))
begin
set @BD = @q3sb;
end
else if((@BD >= @q4sb) and (@BD < @q4eb))
begin
set @BD = @q4sb;
end
declare @q1se datetime; set @q1se = Convert(datetime, '01.01.'+Convert(nvarchar(4), Year(@ED)));
declare @q1ee datetime; set @q1ee = Convert(datetime, '01.04.'+Convert(nvarchar(4), Year(@ED)));
declare @q2se datetime; set @q2se = Convert(datetime, '01.04.'+Convert(nvarchar(4), Year(@ED)));
declare @q2ee datetime; set @q2ee = Convert(datetime, '01.07.'+Convert(nvarchar(4), Year(@ED)));
declare @q3se datetime; set @q3se = Convert(datetime, '01.07.'+Convert(nvarchar(4), Year(@ED)));
declare @q3ee datetime; set @q3ee = Convert(datetime, '01.10.'+Convert(nvarchar(4), Year(@ED)));
declare @q4se datetime; set @q4se = Convert(datetime, '01.10.'+Convert(nvarchar(4), Year(@ED)));
declare @q4ee datetime; set @q4ee = Convert(datetime, '01.01.'+(Convert(nvarchar(4), Year(@ED) + 1)));
if((@ED >= @q1se) and (@ED <= @q1ee))
begin
set @ED = @q1ee;
end
else if((@ED >= @q2se) and (@ED <= @q2ee))
begin
set @ED = @q2ee;
end
else if((@ED >= @q3se) and (@ED <= @q3ee))
begin
set @ED = @q3ee;
end
else if((@ED >= @q4se) and (@ED <= @q4ee))
begin
set @ED = @q4ee;
end
set @Mon = datediff(month, @BD, @ED) / 3;
end
declare @i int;
DECLARE @Mark decimal(24, 2); SET @Mark = 0;
declare @count int; SET @count = 0;
DECLARE @AREA_PATH nvarchar(max), @SI_CheckListId int, @SI_CheckListTitle nvarchar(max), @SI_CheckListCreatedBy int;
DECLARE @Mark2 decimal(24, 2); set @Mark2 = 0;
declare @count2 int; SET @count2 = 0;
DECLARE @areaIdStr nvarchar(max);
set @areaIdStr = convert(nvarchar(max), @AREA_ID);
DECLARE db_cursor_rights2 CURSOR
for
SELECT tbl_SI_CheckList.SI_CheckListId
FROM tblArea INNER JOIN
tbl_SI_CheckList ON tblArea.AREA_ID = tbl_SI_CheckList.AreaId
WHERE ('%/'+tblArea.AREA_PATH+'/%' like '%/'+@areaIdStr+'/%')
and (((tbl_SI_CheckList.SI_CheckListIsDeleted <> 1) and (tbl_SI_CheckList.SI_CheckListDateCreated <= @ED))
or
((tbl_SI_CheckList.SI_CheckListIsDeleted = 1) and (tbl_SI_CheckList.SI_CheckListDateDeleted is not null) and (tbl_SI_CheckList.SI_CheckListDateDeleted >= @BD) and (tbl_SI_CheckList.SI_CheckListDateCreated <= @ED)))
OPEN db_cursor_rights2;
FETCH NEXT FROM db_cursor_rights2
INTO @SI_CheckListId;
WHILE @@FETCH_STATUS = 0
BEGIN
set @i = 1
while @i <= @Mon
begin
set @Mark2 = 0
set @count2 = 0
SELECT @Mark2 = @Mark2 + Mark
FROM tbl_SI_CheckListRegistr
WHERE (SI_CheckListId = @SI_CheckListId) and ((tbl_SI_CheckListRegistr.DateCreated >= @BD) and (tbl_SI_CheckListRegistr.DateCreated < @ED) and (tbl_SI_CheckListRegistr.IsDeleted <> 1))
if(@Mark2 is not null)
begin
set @Mark = @Mark + @Mark2;
end
set @i = @i + 1;
SELECT @count2 = count(Mark)
FROM tbl_SI_CheckListRegistr
WHERE (SI_CheckListId = @SI_CheckListId) and ((tbl_SI_CheckListRegistr.DateCreated >= @BD) and (tbl_SI_CheckListRegistr.DateCreated < @ED) and (tbl_SI_CheckListRegistr.IsDeleted <> 1))
if(@count2 = 0)
begin
set @count2 = @count2 + 1;
end
set @count = @count + @count2;
end
FETCH NEXT FROM db_cursor_rights2
INTO @SI_CheckListId;
END
CLOSE db_cursor_rights2;
DEALLOCATE db_cursor_rights2;
if(@count = 0)
begin
set @count = 1;
end
set @Mark = round(@Mark/@count, 2)
RETURN (@Mark)
END
and this is how i try to call it
DECLARE @AREA_ID int; SET @AREA_ID=1;
DECLARE @BD datetime, @ED datetime;
SET @BD=cast('2012-10-01' AS DATETIME);
SET @ED=cast('2012-11-01' AS DATETIME);
DECLARE @MArk decimal(24,2);
set @Mark = (select dbo.fn_SI_GetMark(@AREA_ID, @BD, @ED));
PRINT @Mark
Else one thing, i tried to execute this function on another dbserver (there is replication set up between this two servers) and it executes very fast. And another one observation if i call function without parameters, set them in function directly, it is also executes fast.
回答1:
Probably old statistics. Try to update
UPDATE STATISTICS tbl_SI_CheckListRegistr
UPDATE STATISTICS tblArea
UPDATE STATISTICS tbl_SI_CheckList
回答2:
Function is yet compiled and is not runing with optimum execution plan. Try to recompile function to improve performance.
RECOMPILE: Forces a new plan to be compiled, used, and discarded after the module is executed. If there is an existing query plan for the module, this plan remains in the cache.
See sp_recompile:
sp_recompile [ @objname = ] 'object'
来源:https://stackoverflow.com/questions/13135069/why-calling-a-function-takes-much-more-time-than-direct-execution-of-function-co