我正在尝试优化一些复杂的视图,这被简化为一个简单的问题。
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'
如果我在左连接中使用谓词日期:
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'
如果我删除/更改了索引IA
或IB
或数据量,则两个计划看起来都不同,但仍然存在:连接表正在读取而没有谓词,查询是慢点。
问题是为什么MSSQL会为这些查询创建不同的计划和如何在第一个示例中更有效地加入?请注意我无法使用第二个查询,因为它只是视图的一部分,其中谓词未知。
关于艾伦的回答还有一个测试。如果我在谓词中仅使用ID
和DATE_FROM
,那么优化程序也会对谓词上的B
进行过滤:
请注意,此更改通常返回不同的结果,但此处返回相同(此处不重要,我猜)。
关于TT的评论(以及Allan的回答)我更改了测试数据以获得更多随机数,因此A.d
并不总是在B
开始的时间间隔内。我只更改了insert into A
:
insert into a (d) values (dateadd(dd, round(rand() * @j, 0), @month));
答案 0 :(得分:1)
我必须把它作为答案,因为它的评论很重要:
SQL Server的做法不同,因为两个查询不一样 对于您而言,它们可能在您的测试示例中是语义上的 - 但它们不适用于优化器/编译器 JOIN子句在WHERE之前处理。这在OUTER JOIN中尤为明显,其中ON子句中的参数与WHERE子句中的参数不同,无论如何....
所以在你说的第一个中 - 给我左边的所有内容并在右边的匹配日期列上做一个外连接(并且在没有匹配的地方给我NULL)。然后在最后它说明日期是特定的东西 但是,在第二个你添加一个额外的约束,并说在左边给我所有,并在右边做一个外连接和日期是在特定日期之间(并在没有匹配的情况下给我NULL)。然后在最后处理WHERE 如此微妙,但显着不同。
您可以很快发现它们不同,因为您不必通过规则引擎进行编译和优化,但引擎必须遵循其规则。
然而,如果没有关于其他内容的更多信息,由于查询的其他部分未显示,我可以给予“优化”的任何建议都可能无关紧要。
根据解释,我认为您甚至最终必须完全重构您的查询 ,如果可能的话,还要重构为多个部分。
然后你可以使用一些临时表(而不是表变量),这样你就可以开始所有INNER JOIN,然后在处理所有INNER JOIN时执行OUTER JOIN。
通过这种方式,您可以过滤掉大量数据,并在OUTER JOIN之上使用临时结果
无论你是否被允许在你的情况下这样做,我都不知道 - 但可能是因为你的“约束”问题是“无法解决的”。