这已被散列并重新散列。除了the tip I pointed out in the comment 和上面发布的链接和解释@xQbert 之外,这里的请求是使用子查询对 COALESCE 与 ISNULL 的解释。让我们考虑这两个查询,它们的结果是相同的:
SELECT COALESCE((SELECT TOP (1) name FROM sys.objects), N'foo');
SELECT ISNULL((SELECT TOP (1) name FROM sys.objects), N'foo');
(关于使用没有 ORDER BY 的 TOP 到 /dev/null/ 的评论。)
在 COALESCE 案例中,逻辑实际上被扩展为这样的:
SELECT CASE WHEN (SELECT TOP (1) ...) IS NULL
THEN (SELECT TOP (1) ...)
ELSE N'foo'
END
使用 ISNULL,这不会发生。有一个内部优化似乎可以确保子查询只被评估一次。我不知道微软以外的任何人是否知道这种优化是如何工作的,但如果你比较计划,你就可以做到这一点。以下是 COALESCE 版本的计划:
这是 ISNULL 版本的计划 - 注意它是多么简单(并且扫描只发生一次):
在 COALESCE 情况下,扫描发生两次。这意味着子查询被评估两次,即使它没有产生任何结果。如果添加 WHERE 子句以使子查询产生 0 行,您将看到类似的差异 - 计划形状可能会发生变化,但您仍会看到双重查找 + 查找或扫描 COALESCE 案例。这是一个稍微不同的例子:
SELECT COALESCE((SELECT TOP (1) name FROM sys.objects
WHERE name = N'no way this exists'), N'foo');
SELECT ISNULL((SELECT TOP (1) name FROM sys.objects
WHERE name = N'no way this exists'), N'foo');
这次 COALESCE 版本的计划 - 你可以再次看到代表子查询的整个分支逐字逐句重复:
还有一个更简单的计划,使用 ISNULL 完成大约一半的工作:
您还可以在 dba.se 上查看此问题以进行更多讨论:
我的建议是这样的(您可以在提示和上述问题中看到我的原因):信任但验证。我总是使用 COALESCE(因为它是 ANSI 标准,支持两个以上的参数,并且不会像数据类型优先级那样做不可靠的事情)除非我知道我使用子查询作为其中之一表达式(我不记得曾经在这样的理论工作之外做过),或者我遇到了一个真正的性能问题,只是想比较一下 COALESCE 与 ISNULL 是否有任何实质性的性能差异(在子查询案例之外,我还没有找到)。由于我几乎总是将 COALESCE 与类似数据类型的参数一起使用,因此我很少需要做任何测试,除了回顾我过去所说的(我也是 the aspfaq article that xQbert pointed out 的作者,7 年前)。