情况下按顺序花费太多时间

时间:2015-05-28 13:50:31

标签: sql sql-server sql-server-2012

我有非常奇怪的行为。如果我的查询有,

,ROW_NUMBER() OVER (ORDER BY CDF.Id) AS [ROW_Number]

然后需要1到2秒。如果我有,

,ROW_NUMBER() OVER (ORDER BY CASE '' WHEN '' THEN CDF.Id END) AS [ROW_Number]

然后需要1到2秒。但是如果我有一个空值变量,

DECLARE @SortExpression varchar(50)=''
,ROW_NUMBER() OVER (ORDER BY CASE @SortExpression WHEN '' THEN CDF.Id END) AS [ROW_Number]

然后需要12到16秒。在我的实际查询中,我在ORDER BY子句中有一些CASE语句CASE WHEN语句。这是我的真实查询,

,ROW_NUMBER() OVER (
                    ORDER BY 
                    CASE WHEN @SortExpression = 'MerchantName' THEN M.Name END ASC,
                    CASE WHEN @SortExpression = '-MerchantName' THEN M.Name END DESC,
                    CASE WHEN @SortExpression = 'Id' THEN CD.Id END ASC,
                    CASE WHEN @SortExpression = '-Id' THEN CD.Id END DESC,
                    CASE WHEN @SortExpression = 'MerchantProductId' THEN CD.MerchantProductId END ASC,
                    CASE WHEN @SortExpression = '-MerchantProductId' THEN CD.MerchantProductId END DESC,
                    CASE WHEN @SortExpression = 'Sku' THEN CD.Sku END ASC,
                    CASE WHEN @SortExpression = '-Sku' THEN CD.Sku END DESC,
                    CASE WHEN @SortExpression = 'ModelNumber' THEN CD.ModelNumber END ASC,
                    CASE WHEN @SortExpression = '-ModelNumber' THEN CD.ModelNumber END DESC,
                    CASE WHEN @SortExpression = 'Offer' THEN CD.Offer END ASC,
                    CASE WHEN @SortExpression = '-Offer' THEN CD.Offer END DESC,
                    CASE WHEN @SortExpression = 'Price' THEN CD.Price END ASC,
                    CASE WHEN @SortExpression = '-Price' THEN CD.Price END DESC,
                    CASE WHEN @SortExpression = 'NewPrice' THEN CD.NewPrice END ASC,
                    CASE WHEN @SortExpression = '-NewPrice' THEN CD.NewPrice END DESC,
                    CASE WHEN @SortExpression = 'InventoryControlType' THEN CD.InventoryControlType END ASC,
                    CASE WHEN @SortExpression = '-InventoryControlType' THEN CD.InventoryControlType END DESC,
                    CASE WHEN @SortExpression = 'Inventory' THEN CD.Inventory END ASC,
                    CASE WHEN @SortExpression = '-Inventory' THEN CD.Inventory END DESC,
                    CASE WHEN @SortExpression = 'Featured' THEN CD.Featured END ASC,
                    CASE WHEN @SortExpression = '-Featured' THEN CD.Featured END DESC,
                    CASE WHEN @SortExpression = 'Visible' THEN CD.Visible END ASC,
                    CASE WHEN @SortExpression = '-Visible' THEN CD.Visible END DESC,
                    CASE WHEN @SortExpression = 'Field1' THEN CD.Field1 END ASC,
                    CASE WHEN @SortExpression = '-Field1' THEN CD.Field1 END DESC,
                    CASE WHEN @SortExpression = 'Field2' THEN CD.Field2 END ASC,
                    CASE WHEN @SortExpression = '-Field2' THEN CD.Field2 END DESC,
                    CASE WHEN @SortExpression = 'Field3' THEN CD.Field3 END ASC,
                    CASE WHEN @SortExpression = '-Field3' THEN CD.Field3 END DESC,
                    CASE WHEN @SortExpression = 'Field4' THEN CD.Field4 END ASC,
                    CASE WHEN @SortExpression = '-Field4' THEN CD.Field4 END DESC,
                    CASE WHEN @SortExpression = 'OutletCode' THEN CD.OutletCode END ASC,
                    CASE WHEN @SortExpression = '-OutletCode' THEN CD.OutletCode END DESC,
                    CASE WHEN @SortExpression = 'Stock' THEN CD.Stock END ASC,
                    CASE WHEN @SortExpression = '-Stock' THEN CD.Stock END DESC,
                    CASE WHEN @SortExpression = 'Order' THEN CD.[Order] END ASC,
                    CASE WHEN @SortExpression = '-Order' THEN CD.[Order] END DESC,
                    CASE WHEN @SortExpression = 'ErrorDescription' THEN CD.[ErrorDescription] END ASC,
                    CASE WHEN @SortExpression = '-ErrorDescription' THEN CD.[ErrorDescription] END DESC,
                    CASE WHEN @SortExpression = 'CreationDateUtc' THEN CD.[CreationDateUtc] END ASC,
                    CASE WHEN @SortExpression = '-CreationDateUtc' THEN CD.[CreationDateUtc] END DESC,
                    CDF.Id, CD.[Order]
) AS [ROW_Number]

3 个答案:

答案 0 :(得分:3)

为了解决第一部分,CASE '' WHEN '' THEN CDF.Id END将在编译时优化为CDF.Id,因此前两个查询是等效的。 在编译时,优化器知道您要按CDF.Id排序,因此可以生成一个利用此索引的计划。

简答

只需添加OPTION (RECOMPILE)查询提示,但这只有在您要排序的列被编入索引时才有用。

完整答案

后一个示例的问题是优化器将根据@SortExpression的未知值创建计划,因此无法计划使用适当的索引,因为排序列未知。

我创建了一个简单的测试DDL:

IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL DROP TABLE dbo.T;
CREATE TABLE dbo.T (A INT, B INT, C INT);
INSERT dbo.T (A, B, C)
SELECT  TOP 100000 
        A = ABS(CHECKSUM(NEWID())) % 1000, 
        B = ABS(CHECKSUM(NEWID())) % 1000, 
        C = ABS(CHECKSUM(NEWID())) % 1000
FROM    sys.all_objects AS a 
        CROSS JOIN sys.all_objects AS b;

CREATE INDEX IX_T_A ON dbo.T (A);
CREATE INDEX IX_T_B ON dbo.T (B);
CREATE INDEX IX_T_C ON dbo.T (C);

作为对照,我跑了:

SELECT  TOP 100 A, B, C,
        D = ROW_NUMBER() OVER (ORDER BY A)
FROM    dbo.T
ORDER BY D;

这给出了一个计划:

enter image description here

这里的关键是优化器知道而不是排序整个表,它只需要索引IX_T_A的前100行,这与排序表相比非常便宜,因为索引已经是排序

这是我们对索引列进行排序的最佳计划。因此,游戏的目标是在使用变量来定义排序时实现此计划。为了进一步解释,我使用了TOP,因为它代表了你想要实现的目标,即过滤一组特定的分页记录:

SELECT  *
FROM    (   SELECT  A, B, C,
                    D = ROW_NUMBER() OVER (ORDER BY A)
            FROM    dbo.T
        ) T
WHERE   D BETWEEN 150 AND 250;

enter image description here

这给出了完全相同的计划,它只是意味着索引搜索在内部进一步开始进入索引。对于其余的测试,我将继续使用TOP,因为它更短。

如上所述,如果我使用变量运行此命令,则查询计划无法使用IX_T_A上的索引扫描,因为它不确定A将是排序列,因此它只使用普通的旧表扫描,并且必须对整个表进行排序,而不是只能从非聚集索引中顺序读取:

DECLARE @Sort VARCHAR(10) = 'A';

SELECT  TOP 100 A, B, C,
        D = ROW_NUMBER() OVER (ORDER BY 
                                CASE WHEN @Sort = 'A' THEN A END ASC,
                                CASE WHEN @Sort = '-A' THEN A END DESC,
                                CASE WHEN @Sort = 'B' THEN B END ASC,
                                CASE WHEN @Sort = '-B' THEN B END DESC,
                                CASE WHEN @Sort = 'C' THEN C END ASC,
                                CASE WHEN @Sort = '-C' THEN C END DESC)
FROM    dbo.T
ORDER BY D;

查询计划:

enter image description here

我能看到的唯一方法是在运行时强制重新编译,以便可以优化冗余排序,并使用正确的索引:

DECLARE @Sort VARCHAR(10) = 'A';
SELECT  TOP 100 A, B, C,
        D = ROW_NUMBER() OVER (ORDER BY 
                                CASE WHEN @Sort = 'A' THEN A END ASC,
                                CASE WHEN @Sort = '-A' THEN A END DESC,
                                CASE WHEN @Sort = 'B' THEN B END ASC,
                                CASE WHEN @Sort = '-B' THEN B END DESC,
                                CASE WHEN @Sort = 'C' THEN C END ASC,
                                CASE WHEN @Sort = '-C' THEN C END DESC)
FROM    dbo.T
ORDER BY D
OPTION (RECOMPILE);

查询计划:

enter image description here

正如您所看到的,当排序列是硬编码时,这已经恢复到计划。在编译时你将有额外的费用,但这应该少于你在运行时看到的额外10s +。

如果您对没有索引的列进行排序,那么无论您是否重新编译都无关紧要,它将使用相同的计划。

答案 1 :(得分:1)

我能想到的唯一选择是需要所有列的索引。我不确定这是否真的可行,但你有所有这些索引,那么以下可能表现良好:

(case when @SortExpression = 'MerchantName'
      then row_number() over (order by MerchantName)
      when @SortExpression = '-MerchantName'
      then row_number() over (order by MerchantName desc)
      . . .
 end)

如果可能,SQL Server足够聪明,可以使用row_number()的索引。而且,我非常确定索引使用是性能差异的根源。即使row_number()case语句中的表达式,它也应该足够智能使用索引。

答案 2 :(得分:1)

我强烈建议您在此处使用动态查询。例如:

declare @mainStatement varchar(1000) = 'select * from sometable'
declare @orderingStatement varchar(1000) = ' order by '

if @SortExpression = 'MerchantName' 
   set @orderingStatement = 'M.Name ASC'
else if @SortExpression = '-MerchantName'
   set @orderingStatement = 'M.Name DESC'
else if
   ......


set @mainStatement = @mainStatement + @orderingStatement 
exec(@mainStatement)

这样你最终会得到我们的查询:

select * from sometable order by M.Name ASC

select * from sometable order by M.Name DESC

您将尽可能地尽力并优化查询。剩下的就是DBA的工作。他将在桌子的列上添加一些缺失的索引并发表声明。