【问题标题】:Query optimization with in (select ...) clause使用 in (select ...) 子句进行查询优化
【发布时间】:2019-12-15 09:34:40
【问题描述】:

我在 Windows 上使用 Firebird WI-V3.0.4.33054。

我在优化这个查询时遇到了问题,它使用了带有 select 的 in 子句:

update CADPC p set p.STA = 'L'
where p.COD in (select distinct CODPC from CADPCI_Rec where IDNfr = 27)
and not exists (select * from CADPCI where CODPC = p.COD)

这个查询的计划是(明显的问题是P NATURAL部分):

PLAN SORT (CADPCI_REC INDEX (PK_CADPCI_REC))
PLAN (CADPCI INDEX (FK_CADPCI_CODPC))
PLAN (P NATURAL)

Select Expression
    -> Filter
        -> Unique Sort (record length: 36, key length: 8)
            -> Filter
                -> Table "CADPCI_REC" Access By ID
                    -> Bitmap
                        -> Index "PK_CADPCI_REC" Range Scan (partial match: 1/3)
Select Expression
    -> Filter
        -> Table "CADPCI" Access By ID
            -> Bitmap
                -> Index "FK_CADPCI_CODPC" Range Scan (full match)
Select Expression
    -> Filter
        -> Table "CADPC" as "P" Full Scan

另一方面,如果我手动运行select distinct,复制结果并粘贴到查询中,如下所示:

update CADPC p set p.STA = 'L'
where p.COD in (5699, 5877, 5985)
and not exists (select * from CADPCI where CODPC = p.COD)

现在优化器为 P 表选择了一个合理的计划并且查询运行得非常快:

PLAN (CADPCI INDEX (FK_CADPCI_CODPC))
PLAN (P INDEX (PK_CADPC, PK_CADPC, PK_CADPC))

Select Expression
    -> Filter
        -> Table "CADPCI" Access By ID
            -> Bitmap
                -> Index "FK_CADPCI_CODPC" Range Scan (full match)
Select Expression
    -> Filter
        -> Table "CADPC" as "P" Access By ID
            -> Bitmap Or
                -> Bitmap Or
                    -> Bitmap
                        -> Index "PK_CADPC" Unique Scan
                    -> Bitmap
                        -> Index "PK_CADPC" Unique Scan
                -> Bitmap
                    -> Index "PK_CADPC" Unique Scan

我也试过两种情况下都存在,但结果是一样的:对每一行重新评估子查询。

update CADPC p set p.STA = 'L'
where exists (select * from CADPCI_Rec where IDNfr = 27 and CODPC = p.COD)
and not exists (select * from CADPCI where CODPC = p.COD)

计划:

PLAN (CADPCI_REC INDEX (PK_CADPCI_REC))
PLAN (CADPCI INDEX (FK_CADPCI_CODPC))
PLAN (P NATURAL)

Select Expression
    -> Filter
        -> Table "CADPCI_REC" Access By ID
            -> Bitmap
                -> Index "PK_CADPCI_REC" Range Scan (partial match: 1/3)
Select Expression
    -> Filter
        -> Table "CADPCI" Access By ID
            -> Bitmap
                -> Index "FK_CADPCI_CODPC" Range Scan (full match)
Select Expression
    -> Filter
        -> Table "CADPC" as "P" Full Scan

所以,问题是:当 in 子句包含一个选择(通常只有几条记录)时,我能否以某种方式让引擎选择索引计划?

【问题讨论】:

  • 问题是Firebird的优化器不区分相关子查询和非相关子查询,所以更新表驱动查询,每行执行子查询。使用merge 可能会更快,我会看看我是否可以在有时间的时候写一个答案,或者找到一个替代选项(合并可能有点冗长)。
  • @MarkRotteveel 您的意思是优化器将“in”转换为“exists”?或者我无法想象如何为要更新的表的“每一行”重新执行子查询。 FB3 的高级计划选项可能会以某种方式告诉优化查询在执行之前“看起来像”什么......我知道,它是 BLR,然后不再是 SQL,但仍然。
  • @Arioch'The 不,据我了解,它将评估表中每一行的条件,并且作为评估的一部分,它将为每一行执行子查询。该行为等同于存在,但它不会将其“转换”为存在。
  • @MarkRotteveel 如果为真,如果“in”查询的结果没有被缓存,但是在每一行上,整个集合都被重新评估为一个整体(而不是检查那一行- 有效地转换为“存在”) - 那么这是最低效的方法,选择两种方法中最糟糕的一面。

标签: sql query-optimization firebird sql-execution-plan firebird-3.0


【解决方案1】:

尝试同时使用exists

update CADPC p
    set p.STA = 'L'
    where exists (select 1 from CADPCI_Rec where r.IDNfr = 27 and p.COD = r.CODPC) and
         not exists (select 1 from CADPCI c2 where c2.CODPC = p.COD);

特别是,您希望在CADPCI_Rec(CODPC, IDNfr) 上建立索引。

【讨论】:

  • 我试过了。两个子查询上的exists 仍然使用P NATURAL 计划,并且没有明显的性能提升。
  • @GabrielF 然后尝试相反的方法,尝试从您的查询中删除 not exists 并且只留下一个 IN 条件,将 CADPCI_Rec 和 CADPCI 都封装在其中
  • 顺便说一句,“在两个子查询上都存在”的计划是什么,充满了吗?
  • @Arioch'我已经编辑了这个问题,包括所有查询的解释计划,包括这个。
【解决方案2】:

你可以试试EXECUTE BLOCK 和“反转控制”

本质上是发布一个匿名的临时存储过程

EXECUTE BLOCK AS
  declare id INTEGER;
BEGIN
  for select distinct t1.CODPC from CADPCI_Rec t1 
    left join CADPCI t2 on where t2.CODPC = t1.CODPC
  where t2.CODPC is NULL and t1.IDNfr = 27
  into :id
  do 
    update CADPC p set p.STA = 'L' where p.COD = :ID and p.STA <> 'L';
END

您还可以使用全局临时表 (GTT)

然后在实际删除之前创建 ID 列表。

数据库准备(创建无主体表):

CREATE GLOBAL TEMPORARY TABLE CADPC_mark_IDs
   ( ID integer )
ON COMMIT DELETE ROWS

然后命令会像

insert into CADPC_mark_IDs(ID)
select distinct t1.CODPC from CADPCI_Rec t1 
   left join CADPCI t2 on where t2.CODPC = t1.CODPC
where t2.CODPC is NULL and t1.IDNfr = 27

然后

update CADPC p set p.STA = 'L'
where p.COD in (select * from CADPC_mark_IDs) and p.STA <> 'L'

然后

commit; -- clear the in-memory table for next uses

另一个选项,就像 Mark 建议的那样,将使用 MERGE,在您将“where not exist”转换为“left join”(已经在上面完成,希望是正确的)之后。

类似的东西

merge into CADPC p
  using (
    select distinct t1.CODPC as id from CADPCI_Rec t1 
      left join CADPCI t2 on where t2.CODPC = t1.CODPC
    where t2.CODPC is NULL and t1.IDNfr = 27
  ) t
on (t.id = p.COD) and (p.STA <> 'L')
when matched then update set p.STA = 'L'

【讨论】:

  • 哇,非常完整的答案!我想到了第一个替代方案,我很确定它会起作用。如果没有其他方法,这绝对是我想要的。第二个我认为行不通,因为问题似乎是in (subquery),它一直存在(无论如何我都会尝试,所以我们肯定知道)。第三种选择,我什至不知道它存在。我明天试试,然后告诉你。
  • @GabrielF 还注意到 FB3 似乎引入了一些显示查询计划的高级选项,可以说更“图形化”。
  • 和我想象的一样,第二种方案还是用plan (P natural)(虽然快了一些,不知道为什么)。
  • 合并解决方案就是其中之一。非常快速和优雅。
  • @GabrielF 如果 Mark 认为主表中的每一行都会一次又一次地重新执行子查询的洞察力成立,它可能会更快。将这些命令与将 ID 缓存在 GTT 中进行明确分离将防止这些重新执行。此外,最好明确阻止更新已标记为 L 的行,set p.STA = 'L' where p.STA &lt;&gt; 'L'
猜你喜欢
  • 2015-02-25
  • 1970-01-01
  • 1970-01-01
  • 2011-12-15
  • 1970-01-01
  • 1970-01-01
  • 2016-06-02
  • 1970-01-01
  • 2020-07-29
相关资源
最近更新 更多