使用 in (select ...) 子句进行查询优化
Query optimization with in (select ...) clause
我在 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 table 和查询 运行 选择一个合理的计划:
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 子句包含 select(通常只有几条记录)时,我能否以某种方式让引擎选择索引计划?
尝试对两者使用 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)
上的索引。
您可以尝试 EXECUTE BLOCK
和 "invert control"
- https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-execblock.html
- https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-forselect
本质上是发布一个匿名的临时存储过程
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 列表。
数据库准备(创建无主体table):
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 建议的那样,在将 "where not exist" 转换为 "left join" 之后使用 MERGE
(上面已经完成,希望正确)。
沿途的事情
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'
我在 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 table 和查询 运行 选择一个合理的计划:
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 子句包含 select(通常只有几条记录)时,我能否以某种方式让引擎选择索引计划?
尝试对两者使用 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)
上的索引。
您可以尝试 EXECUTE BLOCK
和 "invert control"
- https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-execblock.html
- https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-forselect
本质上是发布一个匿名的临时存储过程
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 列表。
数据库准备(创建无主体table):
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 建议的那样,在将 "where not exist" 转换为 "left join" 之后使用 MERGE
(上面已经完成,希望正确)。
沿途的事情
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'