我正在重复Mongus Pong Why would using a temp table be faster than a nested query?提出的问题,该问题没有对我有用的答案。
我们大多数人在某些时候发现,当嵌套查询达到某种复杂性时,需要将其分解为临时表以保持其性能。 荒谬,这可能是最实用的前进方式,意味着这些流程无法再被视为一种观点。通常第三方BI应用程序只能很好地与视图一起使用,因此这是至关重要的。
我确信必须有一个简单的查询计划设置,以使引擎依次为每个子查询假脱机,从内到外工作。没有第二个猜测它如何使子查询更具选择性(它有时非常成功)并且没有相关子查询的可能性。只是程序员打算通过括号之间的自包含代码返回的数据堆栈。
我常常发现,简单地从子查询更改为#table需要120秒到5秒的时间。本质上,优化器在某处犯了一个重大错误。当然,可能有非常耗时的方式我可以诱使优化器以正确的顺序查看表,但即使这样也无法保证。我不是要求理想的2秒执行时间,只是临时表格在视图的灵活性中为我提供的速度。
我以前从未在这里发帖,但我已经写了多年的SQL并且已经阅读了其他有经验的人的评论,他们也刚刚接受了这个问题,现在我想要适当的天才向前迈进并说特殊提示是X ...
答案 0 :(得分:12)
有一些可能的解释,为什么你看到这种行为。一些常见的是
#temp
表可能会强制为计划的该部分提供更优的连接顺序。#temp
表中可以通过纠正差的基数估算值来改进计划的其余部分。最可靠的方法就是使用#temp
表并自己实现它。
关于第1点的失败,请参阅Provide a hint to force intermediate materialization of CTEs or derived tables。使用TOP(large_number) ... ORDER BY
通常可以鼓励结果被假脱机而不是重复评估。
即使它有效,但是没有关于假脱机的统计数据。
对于第2点和第3点,您需要分析为什么没有得到所需的计划。可能重写查询以使用sargable谓词,或更新统计信息可能会获得更好的计划。如果没有,您可以尝试使用查询提示来获得所需的计划。
答案 1 :(得分:5)
我不相信有一个查询提示指示引擎依次假脱机每个子查询。
有OPTION (FORCE ORDER)
查询提示强制引擎按指定的顺序执行JOIN,这可能会诱使它在某些情况下实现该结果。此提示将有时导致更复杂的查询计划,并且引擎始终坚持次优计划。当然,通常应该信任优化器以确定最佳计划。
理想情况下会有一个查询提示允许您将CTE或子查询指定为“物化”或“匿名临时表”,但没有。
答案 2 :(得分: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')
答案 3 :(得分:0)
遇到这个问题后,我发现(在我的情况下)SQL Server正在以不正确的顺序评估条件,因为我有一个可以使用的索引(IDX_CreatedOn
上的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只是忽略它。