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

cro*_*sek 11 join sql-server optimization

我注意到,当 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);
Run Code Online (Sandbox Code Playgroud)

对于此查询,计划显示了良好的行估计(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);
Run Code Online (Sandbox Code Playgroud)

是否可以添加统计信息或提示来改进第一种情况的行估计?我尝试添加具有特定过滤器值(属性 = 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);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

作为一个奇怪但成功的兴趣点,还允许它短路查找:

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);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

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

有人可以验证是否曾经执行过单行探测来帮助进行行估计吗?当单行 PK 查找可以大大提高直方图查找的准确性时(特别是如果存在溢出可能性或历史),将优化限制为仅直方图估计似乎是错误的。当实际查询中有 10 个这样的子连接时,理想情况下它们会并行发生。

附带说明,由于 sql_variant 将其基本类型 (SQL_VARIANT_PROPERTY = BaseType) 存储在字段本身中,我希望 convert() 几乎没有成本,只要它是“直接”可转换的(例如不是字符串到十进制而是 int 到int 或 int 到 bigint)。由于在编译时不知道,但用户可能知道,也许 sql_variants 的“AssumeType(type, ...)”函数将允许更透明地处理它们。

ype*_*eᵀᴹ 7

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

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

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

需要一个(PropertyId, ObjectId, Sequence)包含Value. 我会让它UNIQUE安全。如果返回多行,则查询在运行期间无论如何都会抛出错误,因此最好提前确保不会发生这种情况,并使用唯一索引:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;
Run Code Online (Sandbox Code Playgroud)

查询的第二部分:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
Run Code Online (Sandbox Code Playgroud)

需要一个(PropertyId, ObjectId)包含以下内容的索引Value

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;
Run Code Online (Sandbox Code Playgroud)

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

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


cro*_*sek 5

作为对改进统计的明确问题的部分回答......

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

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

为 sparse 属性创建额外的统计信息(与 IX 统计信息相比有些多余):

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840
Run Code Online (Sandbox Code Playgroud)

原件:

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

移除 convert() 后(正确):

在此处输入图片说明

移除 convert() 后(短路):

在此处输入图片说明

仍然有大约 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;
Run Code Online (Sandbox Code Playgroud)

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

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

在此处输入图片说明

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