我有非常奇怪的行为。如果我的查询有,
,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]
答案 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;
这给出了一个计划:
这里的关键是优化器知道而不是排序整个表,它只需要索引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;
这给出了完全相同的计划,它只是意味着索引搜索在内部进一步开始进入索引。对于其余的测试,我将继续使用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;
查询计划:
我能看到的唯一方法是在运行时强制重新编译,以便可以优化冗余排序,并使用正确的索引:
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);
查询计划:
正如您所看到的,当排序列是硬编码时,这已经恢复到计划。在编译时你将有额外的费用,但这应该少于你在运行时看到的额外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
的工作。他将在桌子的列上添加一些缺失的索引并发表声明。