【问题标题】:ISNULL vs " IS NULL and field = 0"ISNULL 与“IS NULL 和字段 = 0”
【发布时间】:2017-10-06 13:22:24
【问题描述】:

我有一个使用此条件连接表的查询:

(Apps.mfrId = Manufacturer.id OR Apps.mfrId IS NULL OR Apps.mfrId = 0)

查询需要 17 秒才能运行,并且使用分析器,查询会导致 5 到 30 条消息(每次运行不同),错误为“错误:1222,严重性:16,状态:18”。

我保留查询完全相同,但我将上述条件更改为:

(Apps.mfrId = Manufacturer.id OR ISNULL(apps.mfrId, 0) = 0)

...现在与这一更改相同的查询在 140 毫秒内运行并且没有锁定错误。

为什么会发生这种情况?

请注意,在对表 Manufacturer 和 Apps 进行测试之前,我已使用 repair_bebuild 选项运行 DBCC CHECKTABLE,并且还重建了两个表上的索引。

另外请注意,没有其他查询同时针对数据库运行。

这是一个有错误的查询的简化版本:

select top 2000 Apps.object_id
from 
    Manufacturer
    INNER JOIN Apps ON (
        Apps.mfrId = Manufacturer.Id
        OR Apps.mfrId IS NULL
        OR Apps.mfrId = 0 
    ) 
where
    Apps.OBJECT_ID = 6879149

如果我使用“前 1000 名”而不是“前 2000 名”,则查询在 100 多毫秒内完成。

【问题讨论】:

  • 你是否展示了每个查询的执行计划?
  • 连接条件看起来非常奇怪。这是左连接,内连接,???我对为什么您想要一个连接条件感兴趣,如果apps.mfrid 为空,该连接条件将被覆盖。听起来你只是想要一个完整的加入。最终目标是什么?
  • @scsimon 这是一个内部连接。我正在使用我没有设计的表格。 Apps.mfrId 是 Manufacturer.id 的可为空的外键(没有设置外键约束)。由于某种原因,原始设计允许 NULL 或 0,NULL 或 0 具有相同的含义(即没有设置有效值)。
  • @WEI_DBA 我还没有。我不是 DBA,我只是一个卑微的软件开发人员。我会看看计划,但它可能对我没有帮助。
  • 我猜你想在 Apps.mfrId = Manufacturer.id 上离开加入。另外,你的两个例子不一样。我希望 Apps.Field62 不是 Apps.mfrId。当应用程序中没有提供任何值时,您是否打算加入 id 为 0 的行?在这种情况下,您可能需要“ISNULL(apps.mfrId, 0) = Manufacturer.id”。

标签: sql-server sql-server-2008


【解决方案1】:

如果 Apps.mfrId 为空,您将加入每个制造商。

根据您的测试,“OR ISNULL(apps.mfrId, 0) = 0”比“OR Apps.mfrId IS NULL OR Apps.mfrId = 0”便宜

这些看起来确实相当,而且您已经有了解决办法,所以问题是为什么和/或如何使非执行者工作。

谈到这样的性能,如果你所说的一切都是正确的,当我们开始问为什么时,我们开始指向 查询优化器。如果它对相同的参数执行不同的操作,它将具有不同的查询计划。您可能会看到表扫描而不是索引使用情况,或者对性能不佳的其他解释。

我鼓励您比较查询计划,或者让其他人来帮助您了解它的不同之处。但是,您已经可以看出它正在做一些不同的事情。

一种可能性是,当 查询优化器修复 发布时,它们默认情况下不会打开,除非 标志 4199(对于所有修复,或特定修复的其他标志)开启。这是因为一般修复可能对大多数人都有好处,但也可能会破坏在存在先前怪癖的环境中优化的应用程序。

https://dba.stackexchange.com/questions/102292/trace-flag-4199-enable-globally

开启 4199 有帮助吗?

select top 2000 Apps.object_id
from 
    Manufacturer
    INNER JOIN Apps ON (
        Apps.mfrId = Manufacturer.Id
        OR Apps.mfrId IS NULL
        OR Apps.mfrId = 0 
    ) 
where
    Apps.OBJECT_ID = 6879149
OPTION(QUERYTRACEON 4199)

另一个相关主题是参数嗅探。有时,一个查询计划可以被缓存,它对一个参数来说是最佳的,但对另一个参数来说是可怕的。在您的情况下,一个应用程序可能会返回 1 个制造商,但另一个应用程序可能会返回所有制造商,这就是值得一提的原因。这通常表现为相同的代码在给定不同的参数时表现不佳。 您可以尝试关闭参数嗅探或强制重新编译以帮助诊断这是否是问题的一部分。

What are the main differences between OPTION(OPTIMIZE FOR UNKNOWN) and OPTION(RECOMPILE)?

我见过这样的情况,如果感觉查询优化器放弃了细读索引,只是因为语句变得过于复杂。但这在您的示例中似乎不太可能;除了修复了查询优化器错误的主题,但要利用它们,您仍然必须打开查询标志,否则您安装的补丁可能什么都不做。

有时您也可以尝试帮助引导 sql 优化器。如果存在应始终使用的索引,则可以将其作为查询提示给出。

我也很想知道以下内容是否能解决问题:

select top 2000 Apps.object_id
from
(
    select Apps.object_id, Apps.mfrId 
    from Apps
    where Apps.OBJECT_ID = 6879149
) Apps
left join Manufacturer  ON (
        Apps.mfrId = Manufacturer.Id
        OR Apps.mfrId IS NULL
        OR Apps.mfrId = 0 )

如果所有查询优化器修复都打开了,但问题仍然存在,我们不得不问为什么查询优化器会这样做。它只能使用存在的索引,并且只有在存在统计信息的情况下才能确定使用索引是否有益。同时,过时的统计数据会导致错误的选择。定期重建/重组索引和更新统计信息可能会很好。您可能想尝试这样做,看看是否有任何影响。

结论

既然您已经有了一个有效的修复程序,请务必使用它。但是你的问题是为什么两个非常相似的事情会产生非常不同的结果。假设正在使用相同的参数,并且包括您的两个选项非常相似的事实,那么问题指向查询优化器做出了错误的选择。这表明您可能需要安装最新的补丁(如果未安装)并启用 4199 以实际启用您已通过 sql server 补丁安装的所有查询优化器修复。这包括将 OPTION(QUERYTRACEON 4199) 添加到 sql 的底部,或全局启用 4199,或类似的。

【讨论】:

  • 这个想法是这样的。我们有一个应用程序(一个应用程序是在车辆上使用零件的地方)。如果应用程序设置了制造商,则该车辆与该制造商相关联。如果车辆没有制造商集(其 mfrid 为 0 或 null),则它与所有制造商关联。
  • 是的,但是为什么 ISNULL 变化要快数百倍,为什么当其他版本的查询确实出现 5 - 30 1222 错误时它没有出现大量错误 1222。
  • 是的,我已经通过优化器运行它,并使用 SQL 管理工作室中的比较选项来比较执行计划。我了解它向我展示的内容的概念,但在这种情况下它对我来说并不是非常有用。
  • 您提供的查询将查询执行时间从大约 4.5 分钟减少到不到一秒。至于 OPTION(QUERYTRACEON 4199) 语句,我将不得不与我们的 IT 人员交谈,因为查询是由没有足够权限的用户运行的(用户“test”没有运行 DBCC TRACEON 的权限。)。今天早些时候在表格上重建了索引。
  • 将“OPTION(QUERYTRACEON 4199)”附加到我的查询末尾似乎并没有提高查询的性能。
猜你喜欢
  • 2017-12-04
  • 1970-01-01
  • 2011-11-17
  • 2018-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-08
  • 2020-05-28
相关资源
最近更新 更多