Sql server 组合多个表时的SQL Server执行计划

Sql server 组合多个表时的SQL Server执行计划,sql-server,tsql,sql-server-2012,sql-execution-plan,Sql Server,Tsql,Sql Server 2012,Sql Execution Plan,我有一个存储过程,它发出一个类似于下面伪tsql的查询 多个ParentID作为参数csv传入、解析并插入到表变量@i中。对于传入的每个ParentId,我们查找StorageTable并将其包含在@i中。现在,根据StorageTable列的值,我们需要通过ParentId从相应的表Table1、Table2或Table3中提取数据。在多个表之间不存在重复的可能性-因此使用UNION ALL 当我检查实际的执行计划时,我发现我的大部分成本/子树成本(超过一半)都花在了StorageTable上

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

多个ParentID作为参数csv传入、解析并插入到表变量@i中。对于传入的每个ParentId,我们查找StorageTable并将其包含在@i中。现在,根据StorageTable列的值,我们需要通过ParentId从相应的表Table1、Table2或Table3中提取数据。在多个表之间不存在重复的可能性-因此使用UNION ALL

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

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

正如我所料,统计IO没有显示表2的任何读取,但根据实际执行计划,数据访问点似乎很昂贵

在我看来,如果某个特定的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>

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

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

例如,从您所说的统计IO输出来看,访问表2的运算符的实际执行次数是0

然而,该计划可能会估计执行次数=1

选择运算符后,您可以在SSMS的“属性”窗口中看到估计值和实际值

如果计划的某些分支低估了执行次数,您可以尝试使用临时表,以便考虑列统计信息

通过添加一些辅助变量和选项重新编译,您可以获得更具代表性的子树成本,但它们仍然与建模假设和估计一样精确

比如说

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) 

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

您的联接也可以写成:internal JOIN@i as i ON T1.ParentId=i.ParentId和i.StorageTable='Table1',因此不需要子查询。你能添加一个执行计划吗?在这种情况下,执行计划中显示的成本是不可靠的。即使在实际计划中,它们也只是估计成本,因此不一定反映实际运行时成本。请发布实际计划。它可以有任意数量的形状。表变量没有统计信息。在这里,统计数据与选择正确的联接运算符高度相关。即使使用带有统计信息的临时表,统计信息也可能会过时。@usr-可能是一个嵌套循环,以@i作为外部表作为统计信息,IO不会显示对Table2Tanks的任何读取以获得回复。在这种情况下,统计IO可能是实际活动的更好指标?就执行计划细节而言,表2显示了55%的估计操作员成本,估计执行次数=5000我应该提到每个表上都有一个Top5000,实际执行次数=0,估计行数=1,实际行数=0。这似乎高估了死刑的数量,对吧?如果表2不适用,我只是不想在表2上花费资源。啊,我没有注意到您还涉及其他表。如果只是表变量,则除非使用选项RECOMPILE,否则表变量将是1。但如果实际执行=0,则成本也基本为0。这非常有效,使我的执行计划更易于管理。谢谢分享!对于生产代码,您是否建议在这样的情况下包含选项recompiletable提示,以便排除不适用的分支。“重新编译”选项似乎是一个有争议的话题,但在这种情况下可能有意义吗?@JohnRussell-根据问题中的信息,这听起来没有什么好处。因为听起来好像在运行时,不相关的分支实际上没有任何成本。您可以在有提示和没有提示的情况下进行尝试,看看它是否对统计IO/统计时间输出有任何改进。