使用动态order by子句时,SQL Server使用错误的执行计划

时间:2016-12-10 11:14:55

标签: sql sql-server sql-execution-plan

我们有一个存储过程,其中包含如下查询:

   SELECT    
       MyTable.Id, MyTable.Date_Start, 
       MySeccondTable.Name + ' ' + MySeccondTable.Family AS OwnerName
   FROM 
       MyTable 
   INNER JOIN 
       MySeccondTable ON MyTable.UserId = MySeccondTable.Id 
   WHERE
       (@Date_StartMin IS NULL OR MyTable.Date_Start >= @Date_StartMin)         
   ORDER BY
       CASE 
          WHEN (@SortOrder = 'Id') AND (@ASC = 1)   
             THEN MyTable.Id
       END ASC,             
       CASE 
          WHEN (@SortOrder = 'Id') AND (@ASC = 0)  
             THEN MyTable.Id
       END DESC

@Date_StartMin@SortOrder是用户发送到存储过程的参数。

此查询正常运行。但是当我们检查执行计划时,我们发现SQL Server使用的执行计划效率低3倍,执行

当我们将查询更改为:

SELECT    
    MyTable.Id, MyTable.Date_Start, 
    MySeccondTable.Name + ' ' + MySeccondTable.Family AS OwnerName
FROM 
    MyTable 
INNER JOIN 
    MySeccondTable ON MyTable.UserId = MySeccondTable.Id 
WHERE
    (@Date_StartMin IS NULL OR MyTable.Date_Start >= @Date_StartMin)            
ORDER BY
    CASE 
       WHEN (2 > 1) AND (@ASC = 1)  
          THEN MyTable.Id                   
    END ASC ,               
    CASE 
       WHEN (2 > 1) AND (@ASC = 0)  
          THEN MyTable.Id                   
    END DESC

一切都在发生,执行时间也已确定。

那么重点是什么? 我们如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

即使查询看起来像是使用了正确的执行计划,它也可能不适用于所有变量组合。问题是SQL Server缓存执行计划,缓存计划通常基于存储过程的第一次运行。此过程称为"参数嗅探"。

您可以使用with recompile选项来解决这个问题:

SELECT t1.Id, t2.Date_Start, 
       t2.Name + ' ' + t2.Family AS OwnerName
 FROM MyTable t INNER JOIN 
      MySeccondTable t2
      ON t.UserId = t2.Id 
 WHERE (@Date_StartMin IS NULL OR t.Date_Start >= @Date_StartMin)         
 ORDER BY (CASE WHEN (@SortOrder = 'Id') AND (@ASC = 1) THEN t.Id
           END) ASC,             
          (CASE WHEN (@SortOrder = 'Id') AND (@ASC = 0)  
                THEN t.Id
           END) DESC
 OPTION (RECOMPILE);

但是,我不能100%确定ORDER BY中常量的参数嗅探,重新编译和编译器优化如何协同工作。

我可能会使用动态SQL:

declare @sql nvarchar(max);
set @sql = '
    SELECT t1.Id, t2.Date_Start, t2.Name + ' ' + t2.Family AS OwnerName
     FROM MyTable t INNER JOIN 
          MySeccondTable t2
          ON t.UserId = t2.Id 
     WHERE (@Date_StartMin IS NULL OR t.Date_Start >= @Date_StartMin)         
     ORDER BY @SortOrder @Asc
     OPTION (RECOMPILE)';
set @sql = REPLACE(REPLACE(@sql, '@SortOrder', COALESCE(@SortOrder, '')
                          ), '@Asc', COALESCE(@Asc, '')
                  );

exec sp_executesql @sql, N'@Date_StartMin Date', @Date_StartMin = @Date_StartMin;