如何强制子查询和#temp表一样执行?

时间:2013-09-12 13:30:59

标签: sql-server performance sql-server-2008 optimization query-optimization

我正在重复Mongus Pong Why would using a temp table be faster than a nested query?提出的问题,该问题没有对我有用的答案。

我们大多数人在某些时候发现,当嵌套查询达到某种复杂性时,需要将其分解为临时表以保持其性能。 荒谬,这可能是最实用的前进方式,意味着这些流程无法再被视为一种观点。通常第三方BI应用程序只能很好地与视图一起使用,因此这是至关重要的。

我确信必须有一个简单的查询计划设置,以使引擎依次为每个子查询假脱机,从内到外工作。没有第二个猜测它如何使子查询更具选择性(它有时非常成功)并且没有相关子查询的可能性。只是程序员打算通过括号之间的自包含代码返回的数据堆栈。

我常常发现,简单地从子查询更改为#table需要120秒到5秒的时间。本质上,优化器在某处犯了一个重大错误。当然,可能有非常耗时的方式我可以诱使优化器以正确的顺序查看表,但即使这样也无法保证。我不是要求理想的2秒执行时间,只是临时表格在视图的灵活性中为我提供的速度。

我以前从未在这里发帖,但我已经写了多年的SQL并且已经阅读了其他有经验的人的评论,他们也刚刚接受了这个问题,现在我想要适当的天才向前迈进并说特殊提示是X ...

4 个答案:

答案 0 :(得分:12)

有一些可能的解释,为什么你看到这种行为。一些常见的是

  1. 可能会反复重新评估子查询或CTE。
  2. 通过从等式中删除一些可能的选项,将部分结果实现到#temp表可能会强制为计划的该部分提供更优的连接顺序。
  3. 将部分结果具体化到#temp表中可以通过纠正差的基数估算值来改进计划的其余部分。
  4. 最可靠的方法就是使用#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只是忽略它。