今天我有一段可怕的时间试图让查询执行我期望的方式。我不得不对昨天存在于查询中的表值函数稍作修改,并且该更改对查询产生了巨大的性能影响。在评估执行计划并查看统计信息IO和时间之后,我发现由于我更改了函数以返回表变量而不仅仅是结果集,因此它正在对正在查询的其中一个表执行完全扫描。
我的问题是为什么让它返回表(TableVariable)而不仅仅是选择/结果集导致计划发生如此大的变化?
...难倒
答案 0 :(得分:57)
返回一个表变量会使它成为一个多语句表值函数,并且由于它被视为一个表,除了没有可用于SQL Server的统计信息这一事实,因此可能对性能有害基于良好的执行计划 - 所以它将估计函数返回非常少的行。如果它返回的行数较多,那么生成的计划可能远远低于最佳值。
然而,仅返回SELECT使其成为内联表值函数 - 将其视为视图。在这种情况下,实际的基础表将被带入主查询,并且可以基于适当的统计信息生成更好的执行计划。您会注意到,在这种情况下,执行计划根本不会提及函数,因为它基本上只是将函数合并到主查询中。
CSS SQL Server工程师在MSDN上有一个很好的参考,包括(引用):
但是如果你使用多语句TVF, 它被视为另一个 表。因为没有 可用的统计信息,SQL Server有 做出一些假设 一般提供低估计。如果你的 TVF只返回几行,它会 没事的。但如果你打算 成千上万的TVF填充 行和如果这个TVF加入 其他表,效率低下的计划可以 低基数估计的结果。
答案 1 :(得分:5)
这是因为多语句表值UDF不能与其使用的其余SQL状态内联处理,因此不能成为语句缓存计划的一部分。这意味着它必须与对于查询生成的最终结果集 中的每一行,一遍又一遍地使用 的SQL的其余部分。
内联表值UDF,otoh,与其使用的sql一起被处理和编译,因此变为 part 无论您生成多少行,缓存计划只会被处理和编译 一次 。
答案 2 :(得分:3)
如果没有更多信息,真的无法回答。但是,因为我喜欢在黑暗中疯狂刺伤。 。
引擎无法优化表变量 - 引擎始终“假定”表变量在生成执行计划时只有一行。这就是为什么你可能会看到奇怪的表现的一个原因。
答案 3 :(得分:2)
在SQL Server 2014上,我们能够通过将表值函数数据插入临时表然后对其进行连接来解决我们的问题。而不是直接连接到表值函数。
这将我们的执行时间从2分钟提高到4秒。
以下是一个适用于我们团队的示例:
- 慢速查询(2分钟):
DECLARE @id INT = 1;
SELECT *
FROM [data].[someTable] T
INNER JOIN [data].[tableValueFunction](@id) TVF ON TVF.id = T.id;
- 快速查询(4秒):
DECLARE @id INT = 1;
SELECT *
INTO #tableValueFunction
FROM [data].[tableValueFunction](@id) TVF
SELECT *
FROM [data].[someTable] T
INNER JOIN #tableValueFunction TVF ON TVF.id = T.id;
答案 4 :(得分:0)
当使用多语句表值UDF时,该UDF在调用者可以使用其结果之前运行完成。使用内联表值UDF,SQL Server基本上将UDF扩展为调用查询,就像宏扩展一样。除其他外,这具有以下含义:
WHERE
子句可以直接插入到内联表值UDF中,但不能插入到多语句UDF中。因此,如果表值UDF生成了许多将被调用查询的WHERE
子句过滤掉的行,则查询优化器可以将WHERE
子句直接应用于内联表值UDF但不是多语句UDF。VIEW
,而多语句表值UDF的行为类似于您填充,然后在查询中使用表变量。 如果您的UDF返回许多行并且由表支持,我想这可能是表扫描的来源。向UDF添加更多参数以使调用者能够约束其结果大小,或者尝试在UNION
等朋友的帮助下将其重新表示为内联表值UDF。我会不惜一切代价避免多语句表值UDF,除非知道结果大小只有几行和,很难用基于集合的逻辑产生所需的结果。