Sql server 未将谓词推入MSSQL上的左联接
我试图优化一些复杂的视图,这些视图被简化为一个简单的问题 MSSQL连接两个表,部分在主查询谓词上。问题是,服务器不会对连接的表使用此谓词,直到它实际离开连接状态,结果是从表中读取更多数据,并且查询速度较慢 样本数据 为了说明这个问题,我创建了表示部分视图数据的简单示例:Sql server 未将谓词推入MSSQL上的左联接,sql-server,join,query-optimization,predicate,Sql Server,Join,Query Optimization,Predicate,我试图优化一些复杂的视图,这些视图被简化为一个简单的问题 MSSQL连接两个表,部分在主查询谓词上。问题是,服务器不会对连接的表使用此谓词,直到它实际离开连接状态,结果是从表中读取更多数据,并且查询速度较慢 样本数据 为了说明这个问题,我创建了表示部分视图数据的简单示例: create table A ( ID numeric not null identity, D date not null, ); create table B ( ID numeric not null id
create table A (
ID numeric not null identity,
D date not null,
);
create table B (
ID numeric not null identity,
A_ID numeric not null,
DATE_FROM date not null,
DATE_TO date not null
)
declare @i int = 0
declare @j int
declare @k int
declare @batch int = 1000
declare @a_id int
declare @month date
begin transaction
while @i < 2000
begin
set @j = 0
set @month = dateadd(mm, @i, '1950-01-01')
while @j < 20
begin
insert into a (d) values (@month);
select @a_id = scope_identity()
set @k = 0
while @k < 30
begin
insert into b ( a_id, date_from, date_to )
values ( @a_id, @month, dateadd(dd, round(rand() * 100, 0), @month) );
set @k = @k + 1;
if (@batch = 0)
begin
set @batch = 1000
commit;
begin transaction
end
set @batch = @batch - 1;
end
set @j = @j + 1;
end
set @i = @i + 1;
end
commit
alter table A add constraint A_PK primary key (ID);
alter table B add constraint B_PK primary key (ID);
alter table B add constraint A_FK foreign key (A_ID) references A(ID);
create index AI on A(D);
create index BI on B(A_ID, DATE_FROM, DATE_TO) include (ID);
结果大约需要80ms,查询计划如下:
基本相同(快速)查询
如果我在左连接中使用右谓词日期:
select A.id
, B.id
, B.DATE_FROM
, B.DATE_TO
from A
left join B on B.A_ID = A.ID
and '2000-01-01' between B.DATE_FROM and B.DATE_TO
where A.D = '2000-01-01'
突然间,MSSQL可以实际使用它,并将速度提高到0ms:
问题:
如果我删除/更改了索引IA
或IB
或数据量,这两个计划看起来不同,但仍然存在相同的情况:读取联接表时没有谓词,查询速度较慢
问题是为什么MSSQL为这些查询创建不同的计划以及如何在第一个示例中更有效地联接?请注意,我不能使用第二个查询,因为它只是视图的一部分,其中谓词未知
编辑1
关于艾伦的回答,还有一个考验。如果我在谓词中仅使用ID
和DATE\u FROM
,那么优化器也会在谓词上过滤B
:
请注意,此更改返回的结果通常不同,但在这里它返回的结果相同(我想在这里并不重要)
编辑2
关于TT的评论(以及Allan的回答),我更改了测试数据以获得更多随机数,因此A.d
并不总是以B
开始的间隔。我只将插入更改为:
insert into a (d) values (dateadd(dd, round(rand() * @j, 0), @month));
然后优化器开始按预期工作:
我必须把这作为一个答案,因为它的评论越来越重要:
SQL Server的做法有所不同,因为这两个查询不相同。
对于您来说,它们可能在您的测试示例中是语义上的,但它们不是针对优化器/编译器的。
JOIN子句在WHERE之前处理。这在外部联接中尤其明显,其中ON子句中的参数的含义与where子句中的参数的含义不同
所以在你的第一个例子中,你说-在左边给我全部,在匹配的日期列上和右边做一个外部连接(在没有匹配的地方给我NULL)。最后,它会指出具体的日期。
但是,在第二个示例中,您添加了一个额外的约束,并说在左边给我所有,在右边做一个外部联接,并且日期介于特定日期之间(如果没有匹配项,则给我NULL)。然后在最后处理的位置。
如此微妙,但意义重大不同
您可以很快看到它们是不同的,因为您不必通过规则引擎进行编译和优化,但引擎必须遵循其规则
但是,如果没有关于其他情况的更多信息,我对“优化”的任何建议都可能是不相关的,因为查询的其他部分没有显示
根据这一解释,我认为您甚至必须完全重构您的查询,如果可能的话,还必须重构为多个部分。
然后可以利用一些临时表(而不是表变量),这样就可以开始执行所有内部联接,然后在处理所有内部联接时执行外部联接。
这种方式将允许您过滤掉大量数据,并在外部联接的顶部使用临时结果。
我不知道在你的情况下是否允许你这样做,但考虑到你的“限制”,这个问题可能是“无法解决的” 快速查询更好;您不需要在join子句中引用A.D
,您已经知道您希望B字段之间有一个特定的日期。@TT。优化器可以做出这样的决定。宣传平等通常是可以做到的。投票选DBA。纯查询优化问题最好在那个网站上解决。你说连接子句是在WHERE之前处理的,但我不确定你说的是物理操作。请看我问题的编辑1。有没有两个查询返回不同结果的情况?现在想不出什么了。
insert into a (d) values (dateadd(dd, round(rand() * @j, 0), @month));