如何强制子查询像 #temp 表一样执行?
Posted
技术标签:
【中文标题】如何强制子查询像 #temp 表一样执行?【英文标题】:How can I force a subquery to perform as well as a #temp table? 【发布时间】:2013-09-12 13:30:58 【问题描述】:我正在重申 Mongus Pong Why would using a temp table be faster than a nested query? 提出的问题,该问题没有适合我的答案。
我们中的大多数人在某些时候发现,当嵌套查询达到一定的复杂性时,它需要分解为临时表以保持其性能。 荒谬,这可能是最实际的前进方式,并且意味着这些过程不能再被纳入视图。而且通常第 3 方 BI 应用程序只能很好地与视图配合使用,因此这一点至关重要。
我确信必须有一个简单的查询计划设置,以使引擎从内到外依次处理每个子查询。没有第二次猜测它如何使子查询更具选择性(有时它做得非常成功)并且没有相关子查询的可能性。只是程序员打算由括号之间的自包含代码返回的数据堆栈。
我经常会发现,简单地从子查询更改为#table 需要 120 秒到 5 秒的时间。本质上,优化器在某个地方犯了一个重大错误。当然,可能有一些非常耗时的方法可以哄优化器以正确的顺序查看表格,但即使这样也不能保证。我在这里要求的不是理想的 2 秒执行时间,只是在 view 的灵活性范围内临时表为我提供的速度。
我以前从未在这里发过帖子,但我已经写 SQL 多年了,并且阅读了其他有经验的人的 cmets,他们也刚刚接受了这个问题,现在我希望合适的天才能够挺身而出并说特殊提示是 X...
【问题讨论】:
你有一个长时间运行查询的例子吗? 我可以粘贴它,但当我说它不相关时请相信我。该查询具有相似的复杂性,它只需要 8 秒即可运行,而我所做的唯一更改是聚合的细节,它达到了 120 秒。如果我临时表子查询,它仍然运行得很快。我相信如果我在这里粘贴代码,您将修复泰坦尼克号上的躺椅,批评细节,可能会找到一个重大的重写来哄它正常运行,但我在这里谈论的是抽象中的一个原则 - 即我想假脱机子查询的结果。很简单。 你在这里问两个问题“为什么临时表比子查询快?”和“为什么临时表比 CTE 更快?” 以下关于线轴的文章值得一读:sqlblog.com/blogs/rob_farley/archive/2013/06/11/… @RBarryYoung - 这样做的一个缺点是它还假设将发出 1 行。所以它可能会打乱计划其余部分的基数估计。 【参考方案1】:对于您看到这种行为的原因,有几种可能的解释。一些常见的是
-
子查询或 CTE 可能会被反复重新评估。
将部分结果具体化到
#temp
表中可能会通过从等式中删除一些可能的选项来强制为计划的该部分设置更优化的连接顺序。
将部分结果具体化到 #temp
表中可以通过纠正不良的基数估计来改进计划的其余部分。
最可靠的方法是简单地使用#temp
表并自己实现它。
关于第 1 点的失败请参阅Provide a hint to force intermediate materialization of CTEs or derived tables。使用TOP(large_number) ... ORDER BY
通常会鼓励结果被假脱机而不是反复重新评估。
即使这样可行,但在阀芯上没有统计信息。
对于第 2 点和第 3 点,您需要分析未获得所需计划的原因。可能重写查询以使用 sargable 谓词,或者更新统计信息可能会获得更好的计划。如果失败,您可以尝试使用查询提示来获得所需的计划。
【讨论】:
是的!这就是魅力(强制中间物化)。缩短至 4 秒。当然,这似乎是完成一件非常常用的有用事情的一种 hacky 方式,但也许 MS 稍后会将其作为提示或设置。 @Adamantish - 他们还没有因为“无法修复”而关闭它,从那个 POV 来看这是令人鼓舞的。 当我遇到原因 2 和 3 时,您关于线轴上没有统计信息的观点值得牢记。 对于使用“with a as (...), b as (...)”的大型查询,我发现使用“top 1000000 ... order by”似乎没有给出与将子查询转换为临时表相同的加速。这在 MSSQL 2008 R2 上。 对我来说,视图是模块化的基于集合的编程的基本粒子。它们挂在您的索引上,并为优化器提供了从一堆嵌套对象中组合出一个好的计划的最佳机会。不过,我们在这里讨论的是如何挫败优化器,所以这一点可能没有实际意义。【参考方案2】:我不相信有一个查询提示会指示引擎依次假脱机每个子查询。
OPTION (FORCE ORDER)
查询提示强制引擎按照指定的顺序执行 JOIN,这可能会诱使引擎在某些情况下实现该结果。此提示有时会为复杂查询提供更有效的计划,并且引擎会坚持使用次优计划。当然,通常应该信任优化器来确定最佳计划。
理想情况下,会有一个查询提示允许您将 CTE 或子查询指定为“物化”或“匿名临时表”,但没有。
【讨论】:
这就是我担心的答案。引擎可能是以这种方式构建的,有什么实际原因吗?我最好的猜测是,它是磁盘空间真正重要的日子的遗物。也许从本质上讲,视图不仅保证不对数据库进行任何更改,在临时处理磁盘空间上留下轻微的足迹,这仍然很重要。当您已经将视图置于其他进程的中心时,这尤其令人讨厌。 强制命令为我做到了!谢谢【参考方案3】:另一个选择(对于本文的未来读者)是使用用户定义的函数。多语句函数(如How to Share Data between Stored Procedures 中所述)似乎会强制 SQL Server 实现子查询的结果。此外,它们允许您在结果表上指定主键和索引以帮助查询优化器。然后可以在 select 语句中使用此函数作为视图的一部分。例如:
CREATE FUNCTION SalesByStore (@storeid varchar(30))
RETURNS @t TABLE (title varchar(80) NOT NULL PRIMARY KEY,
qty smallint NOT NULL) AS
BEGIN
INSERT @t (title, qty)
SELECT t.title, s.qty
FROM sales s
JOIN titles t ON t.title_id = s.title_id
WHERE s.stor_id = @storeid
RETURN
END
CREATE VIEW SalesData As
SELECT * FROM SalesByStore('6380')
【讨论】:
“此外,它们允许您在结果表上指定主键和索引,以帮助查询优化器。”你能澄清一下吗?链接的文章似乎没有表明用户定义的函数可以生成 PK 或索引。 @EdAvis 多语句TVF的结果表只是一个表变量所以这里的方法适用***.com/a/17385085/73226 没错。在我的示例中,您可以看到为返回表的标题列指定了一个主键。 对不起,我的错误,我只是在寻找“创建”语句:-( 一个很好的建议,在许多情况下都是答案。只是想重申@MartinSmith 在上面的评论中给出的关于这种方法的警告:“虽然这样做的一个缺点是它也会假设会发出 1 行。因此它可能会扰乱计划其余部分的基数估计。” 【参考方案4】:遇到这个问题后,我发现(在我的例子中)SQL Server 以错误的顺序评估条件,因为我有一个可以使用的索引(IDX_CreatedOn
on TableFoo
)。
SELECT bar.*
FROM
(SELECT * FROM TableFoo WHERE Deleted = 1) foo
JOIN TableBar bar ON (bar.FooId = foo.Id)
WHERE
foo.CreatedOn > DATEADD(DAY, -7, GETUTCDATE())
我设法通过强制子查询使用另一个索引(即在没有父查询的情况下执行子查询时使用的索引)来解决它。在我的情况下,我切换到 PK,这对查询没有意义,但允许首先评估子查询中的条件。
SELECT bar.*
FROM
(SELECT * FROM TableFoo WITH (INDEX([PK_Id]) WHERE Deleted = 1) foo
JOIN TableBar bar ON (bar.FooId = foo.Id)
WHERE
foo.CreatedOn > DATEADD(DAY, -7, GETUTCDATE())
通过Deleted
列过滤非常简单,之后通过CreatedOn
过滤少数结果更加容易。通过比较子查询和父查询的实际执行计划,我能够弄清楚。
一个更 hacky 的解决方案(并不真正推荐)是通过使用 TOP
限制结果来强制子查询首先执行,但是如果子查询的结果超过限制,这可能会在未来导致奇怪的问题(您总是可以将限制设置为荒谬的)。不幸的是,TOP 100 PERCENT
不能用于此目的,因为 SQL Server 会忽略它。
【讨论】:
以上是关于如何强制子查询像 #temp 表一样执行?的主要内容,如果未能解决你的问题,请参考以下文章
MySQL学习笔记连接子分页联合查询以及sql语句执行顺序总结