一列上的双范围查询使用错误的搜索谓词

Lud*_*udo 5 sql-server

这个问题出现在现实世界中,但在下面的简单示例中已经存在。我正在使用 SQL Server 2017。

给定一个只有一个非 id 列的表,它只需要一个值:

create table #test (id bigint not null, status smallint not null)

/* Populate with 10000 times value 10 */
;WITH numbers(Number) AS
(SELECT 1 AS Number
 UNION ALL
 SELECT Number+1 FROM numbers where Number<10000
)

insert into #test (id,status)
select number,10 from numbers option(maxrecursion 10000)

/* Create fresh index */
create index index_status on #test ([status])
DBCC SHOW_STATISTICS ("tempdb..#test", 'index_status') WITH HISTOGRAM   
Run Code Online (Sandbox Code Playgroud)

现在我们在此列上查询双范围(0 结果,因为唯一的值在两个范围之外):

select 1
from #test
where status < 10
and status <> 2
Run Code Online (Sandbox Code Playgroud)

选择的执行计划使用索引搜索,

  • 寻求谓词“status < 2 AND status > 2”
  • 谓词“状态 < 10”

因此需要读取所有 10000 行。这让我感到惊讶,因为我可以读取索引统计信息得出结论,“status < 10”上的 Seek Predicate 会更有效,因为这已经过滤掉了所有行。

问题:为什么 Seek Predicate 和 Predicate 选择“错误的方式”?

我对替代方案或解决方法不感兴趣,我只对了解这种执行计划选择感兴趣。例如,我们可以分别在两个范围查询上使用 UNION ALL 重写查询,强制使用两个有效的 Seek Predicates:

select 1
from #test
where status < 2

union all 

select 1
from #test
where status > 2 and status < 10
Run Code Online (Sandbox Code Playgroud)

Ron*_*ldo 2

经过更多测试后,我得出的结论是这可能是一个错误。仅当您使用本地临时表时才会发生该行为。当使用全局临时表或公用表时,查询优化器会生成您期望的计划。

使用公共表时索引查找信息:

公用表索引查找信息

使用全局临时表时索引查找信息:

全局临时表索引查找信息

正如我们所看到的,查询优化器可以选择最有效的执行计划,但是本地临时表的行为发生了一些变化。


由于了解本地临时表和全局临时表或常规表之间是否存在可能影响此行为的差异是另一个主题,因此我提出了另一个问题:

为什么使用本地临时表(而不是全局临时表或常规表)会影响查询优化器选择较差的查询计划?