使用带参数的DISTINCT时选择性能性能下降

LaB*_*cca 5 sql-server performance distinct sql-server-2008

赏金注意事项 - START:

参数SNIFFING(这是在赏金前问题中报告的唯一"想法")不是问题,因为您可以在问题末尾的"更新"部分阅读.问题实际上与sql server如何在使用distinct时为参数化查询创建执行计划有关.我上传了一个非常简单的数据库备份(它的工作原理与SQL Server 2008 R2)这里(您必须下载前先等待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
Run Code Online (Sandbox Code Playgroud)

最后说明:我注意到DSTINCT是问题,我的目标是在两个查询中达到相同的速度(或至少几乎相同的速度).

赏金注 - END:


原始问题:

我注意到两者之间的性能差异很大

-- Case A
select distinct * from table where id > 1
Run Code Online (Sandbox Code Playgroud)

比较(这是我的Delphi应用程序生成的sql)

-- Case B1
exec sp_executesql N'select distinct * from table where id > @P1',N'@P1 int',1
Run Code Online (Sandbox Code Playgroud)

这相当于

-- Case B2
declare @P1 int
set @P1 = 1
select distinct * from table where id > @P1
Run Code Online (Sandbox Code Playgroud)

A的执行速度比B1和B2快得多.如果我删除DISTINCT,性能会变得相同.

你可以对此发表评论吗?

在这里,我发布了一个简单的查询,我在3 INNER JOIN的查询中注意到了这一点.无论如何不是一个复杂的查询.

注意:在A和B1/B2情况下,我期望具有完全相同的性能.

那么使用DISTINCT有一些注意事项吗?

更新:

我尝试使用DBCC TRACEON (4136, -1)(禁用参数嗅探 的标志)禁用参数嗅探,但没有任何变化.因此,在这种情况下,问题并未与参数SNIFFING相关联.任何的想法?

Kev*_*ker 2

问题不在于 DISTINCT 导致参数性能下降,而在于参数化查询中查询的其余部分没有被优化掉,因为优化器不会只使用 1=@IS_ADMINISTRATOR 优化掉所有连接就像 1=1 一样。它不会在没有不同的情况下优化连接,因为它需要根据连接的结果返回重复项。

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

这在我的 2008 服务器上执行得和非参数化查询一样好:

-- 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
Run Code Online (Sandbox Code Playgroud)

从运行您的示例的查询计划中可以清楚地看出,它@IS_ADMINISTRATOR = 1并没有像1=1. 在您的非参数化示例中,联接已完全优化,它仅返回 DOCUMENTS 表中的每个 id (非常简单)。

还存在不同的优化缺失@IS_ADMINISTRATOR <> 1。例如,没有该子句的情况下, LEFT OUTER JOINS 会自动更改为INNER JOINs ,但带有该或子句时,它们会保持原样。OR

另请参阅此答案:SQL LIKE % FOR INTEGERS作为动态 SQL 替代方案。

当然,这并不能真正解释你原来问题中的性能差异,因为你在那里没有 OR 。我认为这是一个疏忽。