谓词没有被推入MSSQL上的左连接

时间:2016-02-23 11:12:31

标签: 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 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);

示例(慢)查询

我基本上试图优化的查询非常简单:

select A.id
,      B.id
,      B.DATE_FROM
,      B.DATE_TO
from      A
left join B on B.A_ID = A.ID and A.D between B.DATE_FROM and B.DATE_TO
where A.D = '2000-01-01'

结果需要 80ms ,并且查询计划如下: enter image description here

基本相同(快速)查询

如果我在左连接中使用谓词日期:

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 enter image description here

问题

如果我删除/更改了索引IAIB或数据量,则两个计划看起来都不同,但仍然存在:连接表正在读取而没有谓词,查询是慢点。

问题是为什么MSSQL会为这些查询创建不同的计划如何在第一个示例中更有效地加入?请注意我无法使用第二个查询,因为它只是视图的一部分,其中谓词未知。

编辑1

关于艾伦的回答还有一个测试。如果我在谓词中仅使用IDDATE_FROM,那么优化程序也会对谓词上的B进行过滤: enter image description here

请注意,此更改通常返回不同的结果,但此处返回相同(此处不重要,我猜)。

编辑2

关于TT的评论(以及Allan的回答)我更改了测试数据以获得更多随机数,因此A.d并不总是在B开始的时间间隔内。我只更改了insert into A

insert into a (d) values (dateadd(dd, round(rand() * @j, 0), @month));

并且优化器开始按预期工作: enter image description here

1 个答案:

答案 0 :(得分:1)

我必须把它作为答案,因为它的评论很重要:

SQL Server的做法不同,因为两个查询不一样 对于您而言,它们可能在您的测试示例中是语义上的 - 但它们不适用于优化器/编译器 JOIN子句在WHERE之前处理。这在OUTER JOIN中尤为明显,其中ON子句中的参数与WHERE子句中的参数不同,无论如何....

所以在你说的第一个中 - 给我左边的所有内容并在右边的匹配日期列上做一个外连接(并且在没有匹配的地方给我NULL)。然后在最后它说明日期是特定的东西 但是,在第二个你添加一个额外的约束,并说在左边给我所有,并在右边做一个外连接和日期是在特定日期之间(并在没有匹配的情况下给我NULL)。然后在最后处理WHERE 如此微妙,但显着不同。

您可以很快发现它们不同,因为您不必通过规则引擎进行编译和优化,但引擎必须遵循其规则。

然而,如果没有关于其他内容的更多信息,由于查询的其他部分未显示,我可以给予“优化”的任何建议都可能无关紧要。

根据解释,我认为您甚至最终必须完全重构您的查询 ,如果可能的话,还要重构为多个部分。
然后你可以使用一些临时表(而不是表变量),这样你就可以开始所有INNER JOIN,然后在处理所有INNER JOIN时执行OUTER JOIN。 通过这种方式,您可以过滤掉大量数据,并在OUTER JOIN之上使用临时结果 无论你是否被允许在你的情况下这样做,我都不知道 - 但可能是因为你的“约束”问题是“无法解决的”。