31

我正在重复 Mongus Pong 提出的问题 为什么使用临时表比嵌套查询更快?没有适合我的答案。

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

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

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

我以前从来没有在这里发过帖子,但我已经写了很多年的 SQL 并且阅读了其他有经验的人的评论,他们也刚刚接受了这个问题,现在我希望合适的天才站出来说特别提示是 X...

4

4 回答 4

14

关于您看到这种行为的原因,有几种可能的解释。一些常见的是

  1. 子查询或 CTE 可能会被反复重新评估。
  2. 通过从等式中删除一些可能的选项,将部分结果具体化到#temp表中可能会强制为计划的该部分提供更优化的连接顺序。
  3. 将部分结果具体化到#temp表格中可以通过纠正不良的基数估计来改进计划的其余部分。

最可靠的方法是简单地使用一个#temp表并自己实现它。

关于第 1 点的失败,请参阅提供提示以强制 CTE 或派生表的中间实现。的使用TOP(large_number) ... ORDER BY通常可以鼓励结果被假脱机而不是反复重新评估。

即使这样可行,但也没有关于阀芯的统计信息。

对于第 2 点和第 3 点,您需要分析您没有获得所需计划的原因。可能重写查询以使用 sargable 谓词,或者更新统计信息可能会获得更好的计划。如果失败,您可以尝试使用查询提示来获取所需的计划。

于 2013-09-12T14:13:42.340 回答
5

我不相信有一个查询提示指示引擎依次假脱机每个子查询。

有一个OPTION (FORCE ORDER)查询提示会强制引擎按照指定的顺序执行 JOIN,这可能会诱使引擎在某些情况下实现该结果。此提示有时会为复杂查询生成更有效的计划,并且引擎会坚持使用次优计划。当然,通常应该信任优化器来确定最佳计划。

理想情况下,会有一个查询提示允许您将 CTE 或子查询指定为“物化”或“匿名临时表”,但没有。

于 2013-09-12T14:31:16.917 回答
3

另一种选择(对于本文的未来读者)是使用用户定义的函数。多语句函数(如如何在存储过程之间共享数据中所述)似乎强制 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')
于 2014-09-29T14:32:24.147 回答
0

遇到这个问题后,我发现(在我的情况下)SQL Server 以错误的顺序评估条件,因为我有一个可以使用的索引(IDX_CreatedOnon 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 只是忽略它。

于 2017-02-23T16:36:57.810 回答