如何减少行估计以减少溢出到tempdb的机会


11

我注意到,当tempdb事件大量溢出(导致查询缓慢)时,对于特定的联接,行估计常常会偏离。我已经看到溢出事件是通过合并和哈希联接发生的,它们通常会将运行时间增加3倍至10倍。这个问题涉及如何在减少溢漏事件机会的假设下改进行估计。

实际行数40k。

对于此查询,计划显示错误的行估计(11.3行):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

对于此查询,该计划显示了良好的行估计(56k行):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

是否可以添加统计信息或提示来改善第一种情况的行估计?我尝试添加具有特定过滤器值(属性= 2840)的统计信息,但要么无法正确组合,要么被忽略,因为在编译时ObjectId未知,并且可能选择了所有ObjectId的平均值。

有什么模式可以先执行探针查询,然后使用该模式来确定行估计,还是必须盲目飞行?

这个特殊的属性在一些对象上具有许多值(40k),在绝大多数对象上具有零。我希望可以为给定的连接指定最大预期行数的提示而感到满意。这是一个普遍困扰的问题,因为某些参数可以作为连接的一部分动态确定,或者可以更好地放置在视图中(不支持变量)。

是否可以调整任何参数以最大程度地减少溢出到tempdb的机会(例如,每个查询的最小内存)?稳健的计划对估计没有影响。

编辑2013.11.06:对评论和其他信息的响应:

这是查询计划图像。警告是关于convert()的基数/搜索谓词的:

在此处输入图片说明 在此处输入图片说明

根据@Aaron Bertrand的评论,我尝试替换convert()作为测试:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

在此处输入图片说明

作为一个奇怪但成功的兴趣点,它也使它可以缩短查找:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

在此处输入图片说明

这两个都列出了正确的键查找,但是只有第一个列出了ObjectId的“输出”。我猜这表明第二个确实短路了吗?

有人可以验证是否曾经执行过单行探针以帮助进行行估计吗?当单行PK查找可以极大地提高直方图中查找的准确性时(特别是在存在泄漏可能性或历史记录的情况下),将优化限制为仅对直方图估计值似乎是错误的。当实际查询中有10个这些子联接时,理想情况下它们将并行发生。

附带说明,由于sql_variant将其基本类型(SQL_VARIANT_PROPERTY = BaseType)存储在字段本身内,所以我期望convert()几乎是无用的,只要它可以“直接”转换(例如,不能将字符串转换为十进制,而是将int转换为int或int到bigint)。由于这在编译时未知,但可能为用户所知,因此sql_variants的“ AssumeType(type,...)”函数将允许对它们进行更透明的处理。


1
最初的猜测是,转换为bigint会使您的估算值不正确(查询计划将在SQL Server 2012中对其进行警告),但另一方面,您的子查询将永远不会返回0或1行以外的任何内容成功查询。看到您的查询计划会很有趣。也许作为XML版本的链接。
Mikael Eriksson 2013年

2
通过内联子查询,您可以获得什么?我建议将其单独拉出总体上更清晰,并且由于它可以带来更好的估计,为什么不仅仅使用该方法呢?
亚伦·伯特兰

2
什么版本的SQL Server?您可以为表提供表和索引DDL和统计信息Blob(单列和多列),以便我们查看问题的详细信息吗?declare @a bigint = 完成后使用拆分查询对我来说似乎是一个自然的解决方案,为什么这是不可接受的?
保罗·怀特9

2
我猜您的设计是一种(非常简单的)EAV设计,它迫使您CONVERT()在列中使用然后将它们加入。在大多数情况下,这当然不是很有效。在这个特定的值中,它只是要转换的一个值,因此这可能不是问题,但是表上有哪些索引?EAV设计通常只有通过适当的索引(通常在狭窄的表中有很多索引)才能表现良好。
ypercubeᵀᴹ

@Paul White,就分手了……这可以很好地解决这种情况。但是对于更通用/更复杂的我,我通常不想放弃并行化和可读性。假设我在一个查询中有10个作为子查询(有些更为复杂),但是在其余查询可以开始之前只有5个需要“成熟”,这是为了避免弄清楚哪五个是子查询。
crokusek

Answers:


7

我不会对溢出,tempdb或提示发表评论,因为查询看起来很简单,不需要太多考虑。我认为,如果有适合查询的索引,SQL-Server的优化器将做得很好。

将您分为两个查询是很好的,因为它显示了哪些索引将是有用的。第一部分:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

需要在一个索引(PropertyId, ObjectId, Sequence)包括Value。我会确保UNIQUE安全。如果返回多行,查询无论如何都会在运行时抛出错误,因此最好使用唯一索引预先确保不会发生这种情况:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

查询的第二部分:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

需要在一个索引(PropertyId, ObjectId)包括Value

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

如果效率没有提高,或者没有使用这些索引,或者在行估计中仍然存在差异,则需要进一步查询此查询。

在这种情况下,转换(需要由EAV设计并将不同的数据类型存储在同一列中)是一个可能的原因,并且您将查询分为两部分的解决方案(如@AAron Bertrand和@Paul White注释)似乎很自然以及要走的路。重新设计以便在各自的列中具有不同的数据类型可能是另一回事。


表格中包含索引-我应该在问题中指出这一点。该示例实际上是一个子联接,它提供了一个更大的查询,这就是为什么有关tempdb溢出的问题大为困扰。
crokusek

5

作为对改善统计的明确问题的部分答案...

请注意,即使对于单独分解的情况,行估计仍然相差10倍(4k与预期的40k)。

对于该属性,统计直方图可能散布得太细,因为它是一个长(垂直)的3.5M行表,并且该特定属性极为稀疏。

为稀疏属性创建一个附加统计信息(在IX统计信息中有些多余):

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

原件:

在此处输入图片说明 在此处输入图片说明

删除convert()(正确):

在此处输入图片说明

删除convert()(short-ciruit):

在此处输入图片说明

仍然相差约2倍,这可能是因为> 99.9%的对象根本没有定义属性2840。实际上,仅对于此测试用例,该属性仅存在于3.5M行表的200k个不同对象中的一个上。真是令人惊讶,它真的如此接近。将过滤器调整为较少的ObjectId,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

嗯,没有变化...支持,在统计数据末尾添加了“ with full scan”(可能是前两个不起作用的原因),是的:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

在此处输入图片说明

好极了。因此,在高度垂直的表中覆盖了广泛的IX,添加其他过滤的统计信息似乎是一个很大的改进(特别是对于稀疏但变化很大的键组合)。


多列统计信息中一些问题的链接。
crokusek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.