【问题标题】:SQL Server Execution plan when combining multiple tables组合多个表时的 SQL Server 执行计划
【发布时间】:2014-02-10 11:32:16
【问题描述】:

我有一个存储过程,它发出类似于下面的查询(伪 tsql)。

多个ParentIds 作为参数(csv) 传入、解析并插入到表变量@i 中。对于传入的每个ParentId,我们查找StorageTable 并将其包含在@i 中。现在,根据StorageTable 列的值,我们需要通过ParentId 从相应的表(Table1Table2Table3)中提取数据。多个表之间没有重复的机会 - 因此UNION ALL

当我检查实际的执行计划时,我发现我的大部分成本/子树成本(超过一半)都花在了 StorageTable 上,甚至没有作为输入提供。

例如,如果我包含一个StorageTable = 'Table1',Table2 的索引扫描将在执行计划中出现高成本。

正如我所料,STATISTICS IO 没有显示对 Table2 的任何读取,但根据实际执行计划,数据访问点看起来很昂贵。

在我看来,如果特定的 StorageTable 不存在,与 @i 的内部连接将返回一个空结果集并“短路”任何额外的工作,不是吗?

有什么解决办法?

DECLARE @i AS TABLE
(
    ParentId INT,
    StorageTable VARCHAR(10)
)

INSERT INTO @i...
INSERT INTO @i...
INSERT INTO @i...

SELECT Col1, Col2, Col3
FROM dbo.Table1 AS T1
INNER JOIN (SELECT * FROM @i WHERE StorageTable = 'Table1') AS I
    ON T1.ParentId = I.ParentId
<joins>
<where clause>

UNION ALL

SELECT Col1, Col2, Col3
FROM dbo.Table2 AS T2
INNER JOIN (SELECT * FROM @i WHERE StorageTable = 'Table2') AS I
    ON T2.ParentId = I.ParentId
<joins>
<where clause>

UNION ALL

SELECT Col1, Col2, Col3
FROM dbo.Table3 AS T3
INNER JOIN (SELECT * FROM @i WHERE StorageTable = 'Table3') AS I
    ON T3.ParentId = I.ParentId
<joins>
<where clause>

【问题讨论】:

  • 您的联接也可以写成:INNER JOIN @i AS I ON T1.ParentId = I.ParentId AND I.StorageTable = 'Table1',因此子查询不是必需的。你能添加一个执行计划吗?
  • 执行计划中显示的成本在这种情况下是不可靠的。即使在实际计划中,它们也只是估计成本,因此不一定反映实际运行时成本。
  • 请发布(实际)计划。它可以有任意数量的形状。
  • 表变量没有统计信息。此处的统计信息与选择正确的连接运算符高度相关。即使您使用带有统计信息的临时表,统计信息也可能会过时。
  • @usr - 大概以@i 作为外部表的嵌套循环STATISTICS IO 没有显示针对Table2 的任何读取

标签: sql-server tsql sql-server-2012 sql-execution-plan


【解决方案1】:

在这种情况下,您几乎应该忽略子树成本。

即使在实际计划中,它们也只是基于估计。

从你所说的STATISTICS IO 输出来看,例如访问Table2 的操作员的实际执行次数是0

但是该计划可能会估计执行次数 = 1。

(选择算子后,您可以在SSMS的属性窗口中查看估计和实际数字)

如果计划的某些分支对执行次数的估计低于,您可以尝试使用#temp 表代替,以便将列统计信息考虑在内。

您可以通过添加一些辅助变量和OPTION (RECOMPILE) 获得更具代表性的子树成本,但它们仍然仅与建模假设和估计值一样准确。

例如

DECLARE @T TABLE(
  X            INT,
  StorageTable VARCHAR(50));

INSERT INTO @T
VALUES      (1, 'Table1')

DECLARE @Branch1Exists BIT = iif(EXISTS(SELECT * FROM @T WHERE StorageTable = 'Table1'), 1, 0)
DECLARE @Branch2Exists BIT = iif(EXISTS(SELECT * FROM @T WHERE StorageTable = 'Table2'), 1, 0)

SELECT X
FROM   @T
       JOIN master..spt_values V
         ON [@T].X = number
WHERE  @Branch1Exists = 1
UNION ALL
SELECT X
FROM   @T
       JOIN sys.objects
         ON [@T].X = object_id
WHERE  @Branch2Exists = 1
OPTION (recompile) 

在编译时删除未执行的计划分支,而不是显示估计单次执行的成本。

【讨论】:

  • 感谢您的回复。在这种情况下,也许 STATISTICS IO 是实际活动的更好指标?至于执行计划的详细信息,表 2 显示了 55% 的估计操作员成本,估计执行次数 = 5000(我应该提到每个 TableX 上都有一个 Top(5000)),实际执行次数 = 0,估计行数= 1,Actual number Of Rows = 0。这似乎高估了执行次数,对吧?如果 Table2 不适用,我只是不想将资源花费在 Table2 上。
  • 啊,我没有注意到您在&lt;joins&gt; 中涉及其他表。如果只是表变量,则估计执行次数为 1,除非您使用 OPTION (RECOMPILE)。但是是的,如果Actual Executions = 0 那么成本基本上也是0
  • 效果很好,让我的执行计划更易于管理。感谢分享!
  • 对于生产代码,您是否建议在这种情况下包括选项(重新编译)表提示,以便排除不适用的分支。重新编译选项似乎是一个值得商榷的话题,但在这种情况下可能有意义吗?
  • @JohnRussell - 根据问题中的信息,这里听起来没有什么好处。因为听起来好像在运行时无关的分支实际上并没有任何成本。您可以尝试有无提示,看看它是否对STATISTICS IO/STATISTICS TIME 输出有任何改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
  • 2011-04-12
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多