检查变量是否为NULL会降低性能

Dmi*_*sev 5 sql-server sqlperformance sql-execution-plan sql-server-2014

我有以下查询:

DECLARE @application_number CHAR(8)= '37832904';
SELECT
    la.LEASE_NUMBER AS lease_number,
    la.[LEASE_APPLICATION] AS application_number,
    tnu.[FOLLOWUP_CODE] AS note_type_code -- catch codes not in codes table
FROM [dbo].[lease_applications] la
LEFT JOIN [dbo].tickler_notes_uniq tnu ON tnu.[ACCOUNT_NUMBER] = la.[ACCOUNT_NUMBER]
WHERE la.LEASE_APPLICATION = @application_number
      OR @application_number IS NULL;



SELECT
    la.LEASE_NUMBER AS lease_number,
    la.[LEASE_APPLICATION] AS application_number,
    tnu.[FOLLOWUP_CODE] AS note_type_code -- catch codes not in codes table
FROM [dbo].[lease_applications] la
LEFT JOIN [dbo].tickler_notes_uniq tnu ON tnu.[ACCOUNT_NUMBER] = la.[ACCOUNT_NUMBER]
WHERE la.LEASE_APPLICATION = @application_number; 
Run Code Online (Sandbox Code Playgroud)

这两个查询之间的唯一区别是,我添加了检查该变量是否为NULL的检查。

这些查询的执行计划是: 在此处输入图片说明

您可以在此处找到图形化计划

问题是。为什么计划如此不同?

更新:

第一个查询的实际执行计划可以在这里找到

OPTION(RECOMPILE)将实际执行计划更改为好的计划。但是,缺点是我的主要目标是使用这些参数创建TVF,然后每个使用该功能的人都应该提供该选项。

还值得一提的是,我的主要目标是创建具有2个参数的TVF。每个参数都可能为null,也可能不是,但其中至少1个应为NOT NULL。这些参数大致相等,它们只是2个表中的不同键,它们无论如何都会给出相同的结果(相同的行数,依此类推)。这就是为什么我想做类似的事情

WHERE (col1 = @param1 OR @param1 IS NULL) AND (col2 = @param2 OR @param2 IS NULL) AND (@param1 IS NOT NULL or @param2 IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)

所以,基本上我对所有记录都不感兴趣

Rig*_*iri 5

对于两个不同的查询,您有两个不同的计划。当您在WHERE子句上有相等条件(la.LEASE_APPLICATION = @application_number)(并且有索引)时,您会得到index seek: 按预期工作,这是有道理的!

另一方面,当您将两个条件写入一个WHERE子句时(la.LEASE_APPLICATION = @application_number OR @application_number IS NULL),查询优化器已选择进行扫描。

即使提供了参数值并且它不为空,正在使用的计划是缓存的计划,它在编译时无法知道参数的实际值。

****如果您有一个存储过程并且您使用参数调用它,就会出现这种情况。使用变量执行简单查询时,情况并非如此。正如@sepupic 所说,变量值不会被嗅探。****

生成计划以处理两种情况:当您的参数有值时以及没有时。

解决您的问题的一种选择是使用,OPTION(RECOMPILE)因为它已在评论中说明。另一种选择是将您的查询分开(例如,有两个不同的存储过程,由第三个“包装器”过程调用),以便它们相应地得到优化,每个都是独立的。

我建议您阅读 Kimberly L. Tripp 的这篇文章:Building High Performance Stored Procedures和 Aaron Bertrand 的另一篇文章:An Updated "Kitchen Sink" Example。我认为这些是解释此类场景的最佳文章。

两篇文章都解释了这种情况、可能存在的问题)以及可能的解决方案,例如选项(重新编译)、动态 sql 或分离存储过程。

祝你好运!

  • @DmitrijKultasev TVF 不是存储过程。当被调用时,它会被它的实际文本替换。这意味着您无法从创建厨房水槽 TVF 中获得任何好处。只是不要这样做。编写一个视图来隐藏查询的复杂性并每次使用*适当的*过滤器。像 EF、Dapper 这样的 ORM 使这变得微不足道 (2认同)