存储过程优化

时间:2010-06-18 13:23:36

标签: sql sql-server performance stored-procedures

我有一个存储过程需要花费大量时间来执行。任何人都可以提出更好的方法,以便获得相同的结果集。

ALTER PROCEDURE [dbo].[spFavoriteRecipesGET] 
@USERID INT, @PAGENUMBER INT, @PAGESIZE INT, @SORTDIRECTION VARCHAR(4), @SORTORDER VARCHAR(4),@FILTERBY INT
AS 
BEGIN

    DECLARE 
        @ROW_START INT
    DECLARE 
        @ROW_END INT
    SET 
        @ROW_START = (@PageNumber-1)* @PageSize+1
    SET 
        @ROW_END =  @PageNumber*@PageSize 

    DECLARE 
        @RecipeCount INT

    DECLARE
        @RESULT_SET_TABLE 
    TABLE
    (
        Id INT NOT NULL IDENTITY(1,1),
        FavoriteRecipeId INT,
        RecipeId INT,
        DateAdded DATETIME,
        Title NVARCHAR(255),
        UrlFriendlyTitle NVARCHAR(250),
        [Description] NVARCHAR(MAX),
        AverageRatingId FLOAT,
        SubmittedById INT,
        SubmittedBy VARCHAR(250),
        RecipeStateId INT,
        RecipeRatingId INT,
        ReviewCount INT,
        TweaksCount INT,
        PhotoCount INT,
        ImageName NVARCHAR(50)              
    )
    INSERT INTO  @RESULT_SET_TABLE
        SELECT 
            FavoriteRecipes.FavoriteRecipeId,
            Recipes.RecipeId,
            FavoriteRecipes.DateAdded,
            Recipes.Title,
            Recipes.UrlFriendlyTitle,
            Recipes.[Description],
            Recipes.AverageRatingId,
            Recipes.SubmittedById,
            COALESCE(users.DisplayName,users.UserName,Recipes.SubmittedBy) As SubmittedBy, 
            Recipes.RecipeStateId,
            RecipeReviews.RecipeRatingId,
            COUNT(RecipeReviews.Review),
            COUNT(RecipeTweaks.Tweak),
            COUNT(Photos.PhotoId),      
            dbo.udfGetRecipePhoto(Recipes.RecipeId) AS ImageName
        FROM
            FavoriteRecipes
        INNER JOIN Recipes ON FavoriteRecipes.RecipeId=Recipes.RecipeId AND Recipes.RecipeStateId <> 3 
        LEFT OUTER JOIN RecipeReviews ON RecipeReviews.RecipeId=Recipes.RecipeId  AND RecipeReviews.ReviewedById=@UserId 
        AND RecipeReviews.RecipeRatingId=   (
                                                SELECT MAX(RecipeReviews.RecipeRatingId)
                                                FROM RecipeReviews
                                                WHERE RecipeReviews.ReviewedById=@UserId
                                                AND RecipeReviews.RecipeId=FavoriteRecipes.RecipeId
                                            )  

        OR RecipeReviews.RecipeRatingId IS NULL 
        LEFT OUTER JOIN  RecipeTweaks ON RecipeTweaks.RecipeId = Recipes.RecipeId AND RecipeTweaks.TweakedById= @UserId 
        LEFT OUTER JOIN Photos ON   Photos.RecipeId = Recipes.RecipeId 
                                    AND Photos.UploadedById = @UserId   AND Photos.RecipeId =  FavoriteRecipes.RecipeId 
                                    AND Photos.PhotoTypeId = 1  

        LEFT OUTER JOIN users ON Recipes.SubmittedById = users.UserId       
        WHERE 
            FavoriteRecipes.UserId=@UserId
        GROUP BY 
            FavoriteRecipes.FavoriteRecipeId,
            Recipes.RecipeId,
            FavoriteRecipes.DateAdded,
            Recipes.Title,
            Recipes.UrlFriendlyTitle,
            Recipes.[Description],
            Recipes.AverageRatingId,
            Recipes.SubmittedById,
            Recipes.SubmittedBy,
            Recipes.RecipeStateId,
            RecipeReviews.RecipeRatingId,
            users.DisplayName,
            users.UserName,
            Recipes.SubmittedBy;

        WITH SortResults
        AS
        (
                SELECT 
                ROW_NUMBER() OVER (
                    ORDER BY  CASE WHEN @SORTDIRECTION = 't' AND @SORTORDER='a' THEN TITLE END ASC,
                          CASE WHEN @SORTDIRECTION = 't' AND @SORTORDER='d' THEN TITLE END DESC,
                          CASE WHEN @SORTDIRECTION = 'r' AND @SORTORDER='a' THEN AverageRatingId END ASC,
                          CASE WHEN @SORTDIRECTION = 'r' AND @SORTORDER='d' THEN AverageRatingId END DESC,
                          CASE WHEN @SORTDIRECTION = 'mr' AND @SORTORDER='a' THEN RecipeRatingId END ASC,
                          CASE WHEN @SORTDIRECTION = 'mr' AND @SORTORDER='d' THEN RecipeRatingId END DESC,
                          CASE WHEN @SORTDIRECTION = 'd' AND @SORTORDER='a' THEN DateAdded END ASC,
                          CASE WHEN @SORTDIRECTION = 'd' AND @SORTORDER='d' THEN DateAdded END DESC
                ) RowNumber,
                FavoriteRecipeId,
                RecipeId,
                DateAdded,
                Title,
                UrlFriendlyTitle,
                [Description], 
                AverageRatingId,
                SubmittedById,
                SubmittedBy, 
                RecipeStateId,
                RecipeRatingId,
                ReviewCount,
                TweaksCount,
                PhotoCount, 
                ImageName           

            FROM 
                @RESULT_SET_TABLE
            WHERE   
                    ((@FILTERBY = 1 AND SubmittedById= @USERID)
                OR  ( @FILTERBY = 2 AND (SubmittedById <> @USERID OR SubmittedById IS NULL))
                OR  ( @FILTERBY <> 1 AND @FILTERBY <> 2))
        )         
        SELECT 
            RowNumber,
            FavoriteRecipeId,
            RecipeId,
            DateAdded,
            Title,
            UrlFriendlyTitle,
            [Description], 
            AverageRatingId,
            SubmittedById,
            SubmittedBy, 
            RecipeStateId,
            RecipeRatingId,
            ReviewCount,
            TweaksCount,
            PhotoCount, 
            ImageName   
        FROM 
            SortResults

        WHERE  
        RowNumber BETWEEN @ROW_START AND @ROW_END 
        print @ROW_START    
        print @ROW_END
       SELECT 
            @RecipeCount=dbo.udfGetFavRecipesCount(@UserId)
        SELECT 
            @RecipeCount AS RecipeCount
        SELECT COUNT(Id) as FilterCount FROM @RESULT_SET_TABLE               
        WHERE       
                    ((@FILTERBY = 1 AND SubmittedById= @USERID)
                OR  (@FILTERBY = 2 AND (SubmittedById <> @USERID OR SubmittedById IS NULL))
                OR  (@FILTERBY <> 1 AND @FILTERBY <> 2))


END

6 个答案:

答案 0 :(得分:1)

您需要查看执行计划以了解时间的进展。它可能是由UDF引起的索引,表扫描,任何数量的事情。当您对计划进行分析时,请尝试将查询分解为更小的部分,以确定您是否可以对其进行改动。

然后了解ROW_NUMBER以了解您是否可以不使用本地表。

答案 1 :(得分:1)

情侣笔记

索引 - 通常当人们创建使用临时表或表变量的过程时,他们无法意识到您可以在这些对象上创建索引,这可能会产生巨大的性能影响。

UDF - 有时查询处理器将有效内联UDF逻辑,有时不会,仔细查看您的查询计划,看看如何处理它。通常,如果您在相关子查询中手动内联此逻辑,则可以大大提高性能。

答案 2 :(得分:1)

正如其他人所说,了解解释计划的唯一方法。看一下代码,这部分看起来很可疑:

    AND RecipeReviews.RecipeRatingId=   (
                                            SELECT MAX(RecipeReviews.RecipeRatingId)
                                            FROM RecipeReviews
                                            WHERE RecipeReviews.ReviewedById=@UserId
                                            AND RecipeReviews.RecipeId=FavoriteRecipes.RecipeId
                                        )

一般来说,在连接条件下做一些非平凡的事情是一个坏主意。我会把它分解为一个子选择,因为它是一个外连接,你可能不得不以某种方式将它与RecipeReviews结合起来。

但是:所有这些都是猜测!说明!测量!

答案 3 :(得分:1)

除了UDF可能性能不佳之外,这行代码还关注我

 LEFT OUTER JOIN RecipeReviews 
 ON RecipeReviews.RecipeId=Recipes.RecipeId  
    AND RecipeReviews.ReviewedById=@UserId  
    AND RecipeReviews.RecipeRatingId=   
        (SELECT MAX(RecipeReviews.RecipeRatingId) 
        FROM RecipeReviews 
        WHERE RecipeReviews.ReviewedById=@UserId 
        AND RecipeReviews.RecipeId=FavoriteRecipes.RecipeId )   
        OR RecipeReviews.RecipeRatingId IS NULL  

将子查询用作连接的一部分通常是一种不好的做法。我强烈怀疑这不是你使用的任何索引。并且OR部分没有任何意义,因为左边的连接让你知道了。

重写它以制作派生表。

如果你有很多记录,临时表通常比表变量表现更好,并且可以(也可能应该)被索引。

答案 4 :(得分:0)

您需要在OR条件周围添加括号。

LEFT OUTER JOIN RecipeReviews
  ON RecipeReviews.RecipeId = Recipes.RecipeId 
    AND RecipeReviews.ReviewedById = @UserId  
    AND 
    -- insert open parenthesis here:
    (
      RecipeReviews.RecipeRatingId = (... subquery ...)
      OR RecipeReviews.RecipeRatingId IS NULL  
      -- insert close parenthesis here:
    )

答案 5 :(得分:0)

我要做的第一件非常简单的事就是将所有声明语句移到顶部。

    DECLARE @ROW_START INT,
        @ROW_END INT,
        @RecipeCount INT

    DECLARE 
    @RESULT_SET_TABLE  
TABLE 
( 
    Id INT NOT NULL IDENTITY(1,1), 
       )

下一部分仍然很简单,就像这样:

AND Recipes.RecipeStateId <> 3
AND RecipeTweaks.TweakedById= @UserId

这可以从连接中取出并移动到where子句。如果可以,请更改&lt;&gt;在一个声明中,以便它可以使用索引。

AND RecipeReviews.RecipeRatingId=   
( 
    SELECT MAX(RecipeReviews.RecipeRatingId) 
    FROM RecipeReviews 
    WHERE RecipeReviews.ReviewedById=@UserId 
        AND RecipeReviews.RecipeId=FavoriteRecipes.RecipeId 
)

这看起来很疯狂,需要完全重做。