内部加入已离开的 view/subquery 时的错误计划

Wrong plan when inner-joining a view/subquery that has left join

我正在尝试构建一个内部连接视图的查询(它的存在是为了可重用性),但显然这个视图具有内部左连接的事实不知何故搞乱了优化器,我真的不能了解原因(指数统计数据已更新)。

下面是一个 MCVE。其实很简单。您可以将其想象成一个简单的客户 (B) - 订单 (C) 设计,其中客户的地址(可选)在另一个 table (A) 中。然后我们有一个视图将客户加入到它的地址 (vw_B)。

元数据和示例数据:

create table A (
    id int not null,
    fieldA char(10) not null,

    constraint pk_A primary key (id)
);

create table B (
    id int not null,
    fieldB char(10) not null,
    idA int,

    constraint pk_B primary key (id),
    constraint fk_A foreign key (idA) references A (id)
);

create view VW_B as
    select b.*, a.fieldA from B
    left join A on a.id = b.idA;

create table C (
    id int not null,
    mydate date not null,
    idB int not null,

    constraint pk_C primary key (id),
    constraint fk_B foreign key (idB) references B (id)
);
create index ix_C on C (mydate);

insert into A (id, fieldA)
with recursive n as (
    select 1 as n from rdb$database
    union all
    select n.n + 1 from n
    where n < 10
)
select n.n, 'A' from n;
SET STATISTICS INDEX PK_A;

insert into B (id, fieldB, idA)
with recursive n as (
    select 1 as n from rdb$database
    union all
    select n.n + 1 from n
    where n < 100
)
select n.n, 'B', IIF(MOD(n.n, 5) = 0, null, MOD(n.n, 10)+1) from n;
SET STATISTICS INDEX PK_B;
SET STATISTICS INDEX FK_A;

insert into C (id, mydate, idB)
with recursive n as (
    select 1 as n from rdb$database
    union all
    select n.n + 1 from n
    where n < 1000
)
select n.n, cast('01.01.2020' as date) + 100*rand(), mod(n.n, 100)+1 from n;
SET STATISTICS INDEX PK_C;
SET STATISTICS INDEX FK_B;
SET STATISTICS INDEX IX_C;

通过这种设计,我想要一个可以连接所有 table 的查询,以便我可以按日期 (c.mydate) 或任何索引的客户信息 ( table乙)。显而易见的选择是 B 和 C 之间的内部连接,它工作正常。但是如果我想将客户的地址添加到结果中,通过使用 vw_B 而不是 B,优化器不再选择最佳计划。

这里有一些查询来证明这一点:

手动加入所有 table 并按日期过滤。优化器工作正常。

select c.*, b.fieldB, a.fieldA from C
inner join B on b.id = c.idB
left join A on a.id = b.idA
where c.mydate = '01.01.2020'

PLAN JOIN (JOIN (C INDEX (IX_C), B INDEX (PK_B)), A INDEX (PK_A))

重用 vw_B 自动加入 A table。优化器在 (VW_B B) 上选择一个自然计划。

select c.*, b.fieldB, b.fieldA from C
inner join VW_B b on b.id = c.idB
where c.mydate = '01.01.2020'

PLAN JOIN (JOIN (B B NATURAL, B A INDEX (PK_A)), C INDEX (FK_B, IX_C))

为什么会这样?我认为这两个查询应该在引擎中产生完全相同的操作。现在,这是一个非常简单的 MVCE,我有非常可重用的更复杂的视图,并且更大的 tables 加入这些视图会导致性能问题。

您有什么建议可以改进 performance/PLAN 选择,同时保留视图提供的可重用性的便利性吗?

服务器版本为WI-V3.0.4.33054

Firebird 优化器不够智能,无法将查询视为等效。

您的视图查询等同于:

select c.*, b.fieldB, a.fieldA from C
inner join (B left join A on a.id = b.idA)
on b.id = c.idB
where c.mydate = '01.01.2020'

这将产生(几乎)相同的计划。所以,问题不在于视图的使用与否本身,而在于 table 表达式的嵌套方式。这改变了引擎评估它们的方式,以及引擎认为可能的连接重新排序。

正如 BrakNicku 在评论中指出的那样,对此没有通用的解决方案。