sqlserver的表变量在没有预估误差的状况下,与物理表可join产生的性能问题

众所周知,在sqlserver中,表变量最大的特性之一就是没有统计信息,没法较为准备预估其数据分布状况,所以不适合参与较为复杂的SQL运算。
当SQL相对简单的时候,使用表变量,在某些场景下,即使是对表变量的预估没有产生误差的状况下,仍旧会有问题。
sqlserver的优化引擎对于表变量的支持十分不友好,再次对表变量的使用产生了警戒。sql

 

测试环境搭建ide

理搭建一个简单的测试环境,来验证本文的想要表达的主题,
测试表TestTableVariable 上有KeyCode1 ~KeyCode5 5个字段,分别建立非汇集索引,
对于数据分布,刻意设计出当前这种场景:KeyCode1 ~KeyCode5的字段值,分别趋于稀疏(非空值的愈来愈少,null值愈来愈多)
以下,写入100W行数据,就能够出来下面要表达的效果了。sqlserver

create table TestTableVariable
(
    Id int identity(1,1),
    KeyCode1 varchar(10),
    KeyCode2 varchar(10),
    KeyCode3 varchar(10),
    KeyCode4 varchar(10),
    KeyCode5 varchar(10),
    CreateDate datetime
)

alter table TestTableVariable
add constraint pk_TestTableVariable primary key(Id) 


create index idx_KeyCode1 on TestTableVariable(KeyCode1)
create index idx_KeyCode2 on TestTableVariable(KeyCode2)
create index idx_KeyCode3 on TestTableVariable(KeyCode3)
create index idx_KeyCode4 on TestTableVariable(KeyCode4)
create index idx_KeyCode5 on TestTableVariable(KeyCode5)

insert into TestTableVariable(KeyCode1,CreateDate) values (CONCAT('XX',CAST(RAND()*1000000 AS INT)),GETDATE())
GO 1000000



update TestTableVariable set KeyCode2 = KeyCode1 where Id%10 = 0
update TestTableVariable set KeyCode3 = KeyCode1 where Id%1000 = 0
update TestTableVariable set KeyCode4 = KeyCode1 where Id%10000= 0
update TestTableVariable set KeyCode5 = KeyCode1 where Id%100000 = 0
GO

 

问题重现测试

对于普通的查询,找一个KeyCode1 ~KeyCode5均有值的条件进行查询,执行计划都在预期之中,都可以用到索引,不过多表述优化

select * from TestTableVariable where KeyCode1 = 'XX156876'
select * from TestTableVariable where KeyCode2 = 'XX156876'
select * from TestTableVariable where KeyCode3 = 'XX156876'
select * from TestTableVariable where KeyCode4 = 'XX156876'
select * from TestTableVariable where KeyCode5 = 'XX156876'

下面将查询条件写入一张表变,让表变量与物理表TestTableVariable进行join
以下语句,分别用KeyCode1 ~KeyCode5进行查询,对于非空值分布相对较多的KeyCode1 ~KeyCode3,作查询的时候,执行计划也在预期之中(索引查找)spa

 

从非空值分布愈来愈少的KeyCode4开始,执行计划开始变成非预期的索引查找,变成了表扫描设计

KeyCode5依旧是非预期的索引查找,也是表扫描code

 

这里不是提出相似问题的解决办法的,固然解决办法也比较简单,
1,添加一个不影响逻辑的条件,至关于简单地改写SQL,以下增长where a.KeyCode5 is not null 筛选条件,由于null值不等于任何值,包括null值,所以增长这个条件不会影响这个SQL的逻辑
2,将表变量的数据写入临时表,让临时表与测试表JOIN,其余不作任何修改
两种方式均可以达到index seek的效果。server

declare @tb table ( KeyCode varchar(10))
insert into @tb values ('XX156876')
select * from TestTableVariable a inner join @tb b on a.KeyCode5 = b.KeyCode
where a.KeyCode5 is not null
go

declare @tb table ( KeyCode varchar(10))
insert into @tb values ('XX156876')
select * into #t from @tb
select * from TestTableVariable a inner join #t b on a.KeyCode5 = b.KeyCode
go

如下是二者的执行计划,都是index seekblog

以上是解决办法,暂不过多表述。

 

存在的疑问

问题就在于:
即使是表变量没有统计信息,sqlserver默认状况下老是会预估为1行(不加任何查询提示),既然预估为1行,在当前状况下也是准确的,不认为是预估出现误差致使执行计划出现非最优。
对于临时表,一样是1行数据,来驱动物理表TestTableVariable,就能够正常使用到index seek,而表变量不行?
再就是,对于TestTableVariable表上的统计信息,通过几个SQL查询事后,触发了统计信息的更新,统计信息也相对准确地预估到了999999行为null,1行是一个特定的值XX156876)

1,对于物理表TestTableVariable与表变量的join,因为NULL值跟任何值对比都是没有结果的,换句话说就是,无论表变量里的数据量有多少,按照统计信息中的预估,这个查询对于TestTableVariable这个表来讲,最多只有1行数据(统计信息中的那个非NULL)的数据参与查询运算
2,对于表变量,既然预估为1行,哪有为何不使用索引查找的方式,就算是用不到索引查找,join双方,按照预估,都只有一行数据参与运算的状况下,为何居然要选择HASH JOIN?

表变量参数join的时候,优化器为何连这么一个简单的推断逻辑都作不到,并无很是复杂的逻辑,或者说数据分布异常的状况在里面,最终选择了最差的执行计划进行运算。
反观临时表,用临时表join的状况下,一切都回归到预期的索引查找,能否认为,sqlserver对表变量的join或者说运算,支持的很是不友好(2012~2016均没有改善)。

 

后面怀疑是否是KeyCode5上的统计信息取样百分比不够大,形成的执行计划错误,尝试100%取样

继续测试,问题依旧

当前这个case,并非那种经典的,由于对表变量预估误差形成的执行计划错误,暂时也没法理解,sqlserver为何会对表变量参数参与的join,在当前这种case中,采用如此保守的执行方式。

 

愈来愈多的case证实,在sqlserver中使用表变量参与join,就比如是一颗定时炸弹,随时能够引爆你的系统,看来要慎重。

相关文章
相关标签/搜索