Sql server 使用DISTINCT with参数时,选择STATTION performance DEGRADICATION(状态性能降级)

Sql server 使用DISTINCT with参数时,选择STATTION performance DEGRADICATION(状态性能降级),sql-server,performance,sql-server-2008,distinct,Sql Server,Performance,Sql Server 2008,Distinct,赏金注意事项-开始: 参数嗅探(这是在悬赏前问题中报告的唯一“想法”)不是这里的问题,您可以在问题末尾的“更新”部分中看到。问题实际上与sql server在使用distinct时如何为参数化查询创建执行计划有关。 我上传了一个非常简单的数据库备份(它与SQLServer2008R2配合使用)(下载前必须等待20秒)。针对此数据库,您可以尝试运行以下查询: -- PARAMETRIZED QUERY declare @IS_ADMINISTRATOR int declare @User_ID

赏金注意事项-开始:

参数嗅探(这是在悬赏前问题中报告的唯一“想法”)不是这里的问题,您可以在问题末尾的“更新”部分中看到。问题实际上与sql server在使用distinct时如何为参数化查询创建执行计划有关。 我上传了一个非常简单的数据库备份(它与SQLServer2008R2配合使用)(下载前必须等待20秒)。针对此数据库,您可以尝试运行以下查询:

-- PARAMETRIZED QUERY

declare @IS_ADMINISTRATOR int
declare @User_ID int
set @IS_ADMINISTRATOR = 1 -- 1 for administrator 0 for normal
set @User_ID = 50

SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!!
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  1 = @IS_ADMINISTRATOR OR  ROL.USER_ID = @USER_ID

-- NON PARAMETRIZED QUERY

SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!! 
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  1 = 1 OR  ROL.USER_ID = 50
最后一点注意:我注意到DSTINCT是个问题,我的目标是在两个查询中实现相同的速度(或至少几乎相同的速度)

悬赏注意事项-结束:


原始问题:

我注意到这两种方法在性能上有很大差异

-- Case A
select distinct * from table where id > 1
与比较(这是由我的Delphi应用程序生成的sql)

这相当于

-- Case B2
declare @P1 int
set @P1 = 1
select distinct * from table where id > @P1
A的执行速度比B1和B2快得多。如果我删除DISTINCT,性能将变得相同

请问您对此有何评论?

这里我发布了一个简单的查询,我在一个有3个内部连接的查询中注意到了这一点。无论如何,这不是一个复杂的查询

注意:我希望在案例A和B1/B2中具有完全相同的性能

那么,在使用DISTINCT时是否有一些注意事项

更新

我尝试使用DBCC-TRACEON(4136,-1)(禁用参数嗅探的标志)禁用参数嗅探,但没有任何变化。因此,在这种情况下,问题与参数嗅探无关。有什么想法吗?

但也请参见“参数嗅探”问题


您是否尝试过在不使用动态SQL的情况下运行第二个(较慢的)查询?是否已清除缓存并重新运行第一个查询?您可能遇到了参数化的动态SQL查询


我认为
DISTINCT
是一个转移视线的问题,而不是实际问题。

问题并不是因为DISTINCT导致参数性能下降,这是因为在参数化查询中没有优化查询的其余部分,因为优化器不会像仅使用1=1那样使用1=@IS\u管理员优化所有连接。它不会在没有distinct的情况下优化连接,因为它需要根据连接的结果返回重复的连接

为什么??因为抛出所有联接的执行计划对于@IS_ADMINISTRATOR=1以外的任何值都是无效的。无论是否缓存计划,它都不会生成该计划

这与my 2008 server上的非参数化查询一样执行:

-- PARAMETRIZED QUERY

declare @IS_ADMINISTRATOR int
declare @User_ID int
set @IS_ADMINISTRATOR = 1 -- 1 for administrator 0 for normal
set @User_ID = 50

IF 1 = @IS_ADMINISTRATOR 
BEGIN
SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!!
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  1 = 1
END
ELSE 
BEGIN
SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!!
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  ROL.USER_ID = @USER_ID
END
从运行您的示例时看到的查询计划中可以清楚地看到,
@is\u ADMINISTRATOR=1
没有像
1=1
那样得到优化。在您的非参数化示例中,联接已完全优化,它只返回DOCUMENTS表中的每个id(非常简单)

@是\u ADMINISTRATOR 1
时,还缺少不同的优化。例如,
LEFT-OUTER-JOIN
S自动更改为
internal-JOIN
S,而不使用该
子句,但它们与该OR子句保持原样

另请参见此答案:了解动态SQL替代方案


当然,这并不能真正解释原始问题中的性能差异,因为您没有OR。我想这是一个疏忽。

我从来没有对select distinct使用过“*”。当您在选择原因中包含ID(可能是PK?)时,不是每一行都是不同的,因此请求distinct是没有意义的吗?不,如果我包含ID(当然这是我的真实情况),问题就在那里。我不明白。如果您在select子句中包含PK(即当您选择星号时),则不需要使用DISTINCT,对吗?由于PK列,所有行在任何情况下都是不同的。您可以发布这两个查询的执行计划吗?要正确测试SQL,您不应该清除缓存。您应该使缓存处于模拟真实SQL执行状态的状态。使用清除缓存与否,查询的执行可能会有很大的不同,但在实际环境中执行时,您是否希望使用清除缓存?没有清除缓存会产生差异,但可能是因为参数嗅探仅在使用清除缓存时才会导致“愚蠢”的查询计划。是否有方法告诉SQL Server在B和a中创建相同的执行计划?@Idsandon-如果要比较两个查询以确定哪个查询更有效,为什么不清除缓存。我这样问是因为如果他以前运行过查询a,并且当他再次运行它时它运行得很快,但是查询B第一次运行得很慢,这可能是因为缓存的查询计划。公平的比较此外,我无法避免动态SQL,因为Delphi为我生成动态SQL。当然,我可以避免在Delphi中使用ParmByName,而使用文本搜索和替换,但我希望有另一种解决方案!你给出的第一个链接非常相似。这里的问题是,只有使用distinct我才能看到问题。无论如何,我尝试了DBCC TRACEON(4136,-1),我没有什么不同。所以这是另一个问题!是的,我明白了。谢谢你的解释。至少现在我离答案更近了。我现在清楚地看到,非参数化查询的性能更好,因为1=1的非参数化查询更简单。然后,关于为什么distinct的性能更差,这是sql server的内部工作,因此,正如您所建议的,我将检查查询,或者当然考虑发送非参数化查询。
-- PARAMETRIZED QUERY

declare @IS_ADMINISTRATOR int
declare @User_ID int
set @IS_ADMINISTRATOR = 1 -- 1 for administrator 0 for normal
set @User_ID = 50

IF 1 = @IS_ADMINISTRATOR 
BEGIN
SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!!
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  1 = 1
END
ELSE 
BEGIN
SELECT DISTINCT -- PLEASE REMEMBER DISTINCT MAKES THE DIFFERENCE!!!
  DOC.DOCUMENT_ID
FROM
  DOCUMENTS DOC LEFT OUTER JOIN
  FOLDERS FOL ON FOL.FOLDER_ID = DOC.FOLDER_ID LEFT OUTER JOIN
  ROLES ROL ON (FOL.FOLDER_ID = ROL.FOLDER_ID)   
WHERE
  ROL.USER_ID = @USER_ID
END