SQL Server - stored procedure suddenly become slow
Asked Answered
L

3

6

I have written a stored procedure that, yesterday, typically completed in under a second. Today, it takes about 18 seconds. I ran into the problem yesterday as well, and it seemed to be solved by DROPing and re-CREATEing the stored procedure. Today, that trick doesn't appear to be working. :(

Interestingly, if I copy the body of the stored procedure and execute it as a straightforward query it completes quickly. It seems to be the fact that it's a stored procedure that's slowing it down...!

Does anyone know what the problem might be? I've searched for answers, but often they recommend running it through Query Analyser, but I don't have have it - I'm using SQL Server 2008 Express for now.

The stored procedure is as follows;

ALTER PROCEDURE [dbo].[spGetPOIs]
    @lat1 float,
    @lon1 float,
    @lat2 float,
    @lon2 float,
    @minLOD tinyint, 
    @maxLOD tinyint,
    @exact bit
AS
BEGIN
    -- Create the query rectangle as a polygon
    DECLARE @bounds geography;
    SET @bounds = dbo.fnGetRectangleGeographyFromLatLons(@lat1, @lon1, @lat2, @lon2);

    -- Perform the selection
    if (@exact = 0)
    BEGIN
        SELECT [ID], [Name], [Type], [Data], [MinLOD], [MaxLOD], [Location].[Lat] AS [Latitude], [Location].[Long] AS [Longitude], [SourceID]
        FROM [POIs]
        WHERE
            NOT ((@maxLOD  [MaxLOD])) AND
            (@bounds.Filter([Location]) = 1)
    END
    ELSE
    BEGIN
        SELECT [ID], [Name], [Type], [Data], [MinLOD], [MaxLOD], [Location].[Lat] AS [Latitude], [Location].[Long] AS [Longitude], [SourceID]
        FROM [POIs]
        WHERE
            NOT ((@maxLOD  [MaxLOD])) AND
            (@bounds.STIntersects([Location]) = 1)
    END

END

The 'POI' table has an index on MinLOD, MaxLOD, and a spatial index on Location.

Lactation answered 17/3, 2010 at 12:58 Comment(0)
S
4

Ah, can it be the query plan sucks?

SP's get compiled / query plan determined on FIRST USE - depending on parameters. So, the parameters of the first call (when no plan is present) determine the query plan. At one point I gets dropped from cache, new plan generated.

Next time it runs slow, possibly make a call using query analyzer and get the selected plan - and check how it looks.

if it is this - put in an option to recompile the SP on every call (with recompile).

Seavey answered 17/3, 2010 at 13:3 Comment(3)
This may well be it. I altered the stored procedure to recompile with every call and it seems (FOR NOW!) to have sped things up significantly. Thanks! Is there a way to avoid recompilation AND bad query plans?Lactation
No. What you can do is dump the stored procedure and use dynamic sql... so the query plans get regenerated every call. This is a known issue (tough not all seem to understand it, like KM) with stored procedures - they sometimes turn into REALLY bad query plans.Seavey
There's another option on 2008: 'option (optimize for unknown)'. It avoids the 'first use' issue and does not involve a recompile every time. See blogs.msdn.microsoft.com/sqlprogrammability/2008/11/26/…Familiarize
C
2

parameter sniffing google it. try this, which will "remap" the input parameters to local variables to prevent SQL Server from trying to guess the query plan based on parameters:

ALTER PROCEDURE [dbo].[spGetPOIs]
    @lat1 float,
    @lon1 float,
    @lat2 float,
    @lon2 float,
    @minLOD tinyint, 
    @maxLOD tinyint,
    @exact bit
AS
BEGIN
DECLARE @X_lat1 float,
    @X_lon1 float,
    @X_lat2 float,
    @X_lon2 float,
    @X_minLOD tinyint, 
    @X_maxLOD tinyint,
    @X_exact bit



    -- Create the query rectangle as a polygon
    DECLARE @bounds geography;
    SET @bounds = dbo.fnGetRectangleGeographyFromLatLons(@X_lat1, @X_lon1, @lX_at2, @X_lon2);

    -- Perform the selection
    if (@exact = 0)
    BEGIN
        SELECT [ID], [Name], [Type], [Data], [MinLOD], [MaxLOD], [Location].[Lat] AS [Latitude], [Location].[Long] AS [Longitude], [SourceID]
        FROM [POIs]
        WHERE
            NOT ((@X_maxLOD  [MaxLOD])) AND
            (@bounds.Filter([Location]) = 1)
    END
    ELSE
    BEGIN
        SELECT [ID], [Name], [Type], [Data], [MinLOD], [MaxLOD], [Location].[Lat] AS [Latitude], [Location].[Long] AS [Longitude], [SourceID]
        FROM [POIs]
        WHERE
            NOT ((@X_maxLOD  [MaxLOD])) AND
            (@bounds.STIntersects([Location]) = 1)
    END

END
Campbellbannerman answered 17/3, 2010 at 13:3 Comment(1)
I tried that already, and it didn't seem to make a difference. I'll have to give it another go.Lactation
N
0

I had a similar problem and it was related with indexes.
Rebuilding them help the SP to run fast again.

I found the solution here

USE master;
GO

CREATE PROC DatabaseReIndex(@Database VARCHAR(100)) AS 
BEGIN
  DECLARE @DbID SMALLINT=DB_ID(@Database)--Get Database ID
  IF EXISTS(SELECT * FROM tempdb.sys.objects WHERE name='Indexes') 
  BEGIN --Delete Temp Table if exists, then create
    DROP TABLE TempDb.dbo.Indexes
  END

CREATE TABLE TempDb.dbo.Indexes(IndexTempID INT IDENTITY(1,1),SchemaName NVARCHAR(128),TableName NVARCHAR(128),IndexName NVARCHAR(128),IndexFrag FLOAT)
EXEC ('USE '+@Database+';
INSERT INTO TempDb.dbo.Indexes(TableName,SchemaName,IndexName,IndexFrag)
SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName,sch.name,ind.name IndexName,indexstats.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats('+@DbID+', NULL, NULL, NULL, NULL) indexstats
INNER JOIN sys.indexes ind ON ind.object_id = indexstats.object_id AND ind.index_id = indexstats.index_id
INNER JOIN sys.objects obj on obj.object_id=indexstats.object_id
INNER JOIN sys.schemas as sch ON sch.schema_id = obj.schema_id
WHERE indexstats.avg_fragmentation_in_percent > 10 AND indexstats.index_type_desc<>''HEAP''
ORDER BY indexstats.avg_fragmentation_in_percent DESC')--Get index data and fragmentation, set the percentage as high or low as you need

DECLARE @IndexTempID BIGINT=0,@SchemaName NVARCHAR(128),@TableName NVARCHAR(128),@IndexName NVARCHAR(128),@IndexFrag FLOAT
SELECT * FROM TempDb.dbo.Indexes --View your results, comment out if not needed...

-- Loop through the indexes
WHILE @IndexTempID IS NOT NULL 
  BEGIN
    SELECT @SchemaName=SchemaName,@TableName=TableName,@IndexName=IndexName,@IndexFrag=IndexFrag FROM TempDb.dbo.Indexes WHERE IndexTempID=@IndexTempID
    IF @IndexName IS NOT NULL AND @SchemaName IS NOT NULL AND @TableName IS NOT NULL 
    BEGIN
      IF @IndexFrag<30. 
      BEGIN --Low fragmentation can use re-organise, set at 30 as per most articles
        PRINT 'USE '+@Database+'; ALTER INDEX ' + @IndexName + N' ON ' + @SchemaName + N'.' + @TableName + N' REORGANIZE'
        EXEC('USE '+@Database+'; ALTER INDEX ' + @IndexName + N' ON ' + @SchemaName + N'.' + @TableName + N' REORGANIZE')
      END
    ELSE 
      BEGIN --High fragmentation needs re-build
        PRINT 'USE '+@Database+'; ALTER INDEX ' + @IndexName + N' ON ' + @SchemaName + N'.' + @TableName + N' REBUILD'
        EXEC('USE '+@Database+'; ALTER INDEX ' + @IndexName + N' ON ' + @SchemaName + N'.' + @TableName + N' REBUILD')
      END
    END

    SET @IndexTempID=(SELECT MIN(IndexTempID) FROM TempDb.dbo.Indexes WHERE IndexTempID>@IndexTempID)
  END
END

DROP TABLE TempDb.dbo.Indexes

GO
Neill answered 12/4, 2017 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.