WHERE IN 使用 Table 类型比使用 SQL 服务器中的硬编码值执行得更好

WHERE IN performs much better with Table Type than with hardcoded values in SQL Server

我有 2 个基本相同的查询(至少如果我没有遗漏任何内容)。

DECLARE @siloIds SiloIdsTableType
INSERT INTO @siloIds VALUES 
(1),(2),(3)

-- Query 1
SELECT *
FROM [Transaction]
WHERE 
      SiloId IN (1,2,3)
  AND Time > '2000-02-01'

-- Query 2
SELECT *
FROM [Transaction]
WHERE 
       SiloId IN (select SiloId from @siloIds)
   AND Time > '2000-02-01'

我在想一个人不能击败查询本身声明的常量,但显然第一个查询比第二个查询慢几倍。似乎 SQL 服务器不够智能,无法为硬编码值提供良好的计划,或者我在这里遗漏了什么?

长长的列表似乎不应该使用where in,TVP应该总是受到青睐

P.S。我在查询中使用千值而不是 1,2,3

P.P.S。我在 SiloId ASC、Time ASC 上有一个非聚集索引,但由于某种原因,第一个查询似乎没有使用它来支持聚集索引扫描。

P.P.P.S。执行计划分担成本 14% 到 86% 有利于第二个查询

执行计划:

当您使用 table 变量(或 TVP,两者是一回事)时,SQL 服务器使用固定估计,它只会从中获取 1 行(更多信息下面这个),基数为 1。这意味着它假设 SiloId 连接过滤器非常有选择性,它将优先考虑它并进行嵌套循环连接以获取那些行,然后在 Time 上过滤.

而当您使用常量时,确切的大小是硬编码的。无论出于何种原因(可能是错误的统计数据),它都假定 Time 更具选择性,因此优先于其他过滤器。

table 变量计划失败的地方是其中有很多行,或者在主 table 中,因为那样你会得到很多键查找,这可以慢一点。

理想情况下,您希望编译器预先知道 table 变量的大小。您可以通过多种方式执行此操作,如 Brent Ozar explains:

  • 跟踪标志 2453,如果基数非常不同,这将导致重新编译(如果您可以冒 TF 的风险,这是个好主意)
  • OPTION (RECOMPILE)(这个每次都要重新编译,本身可能效率不高)
  • 一个临时的table(不可能作为参数)