如何为Firebird的subselect优化SQL删除查询?

问题描述

以下查询非常慢。似乎对表中的每一行都执行了subselect?!

delete from HISTORY
where ID in (
  select ID from (
    select ID,ROW_NUMBER() over(partition by SOURCE order by ID desc) as NUM from HISTORY
  ) where NUM > 100
);

这是一个清理查询。它应该删除每个SOURCE上除100条最新记录以外的所有内容

所需时间似乎仅取决于表中的记录数,而不取决于要删除的记录数。即使只有10,000条记录,也要花费几分钟。但是,如果我仅执行子选择,则速度很快。

当然在ID上有一个PK,在SOURCE上有一个FK和索引(都是整数列)。

解决方法

Firebird 3在DELETE子句中添加了MERGE选项。在Release Notes中提到过。另外,pages 16-17的俄语概述中还提供了使用示例。

通过该示例建模,清理查询如下所示:

merge into HISTORY HDel
using ( select ID,SOURCE,ROW_NUMBER() over
                (partition by SOURCE order by ID desc) as NUM 
        from HISTORY ) HVal
   on (HVal.NUM > 100) and (HVal.ID = HDel.ID) and (HVal.Source = HDel.Source)
WHEN MATCHED THEN DELETE

在您的特定数据库中,(HVal.Source = HDel.Source)过滤似乎是多余的,但是我仍然决定添加它,以使查询对于后继读者来说像possibe一样通用。安全胜过遗憾:-)


Firebird 2.x SQL语言手册显示了一个带有MERGE/DELETE的示例,但这可能是一个文档错误-FB 2.5.9 RelNotes不显示该功能被反向移植。

无论如何,由于缺少MERGE / DELETE和Windows Functions功能,因此可以回到明确的命令式编程并编写良好的旧循环。将需要编写并执行一个小的PSQL程序(永久性的命名存储过程或临时EXECUTE BLOCK语句),并在SOURCE值上进行显式循环。

类似的东西(我没有对它进行语法检查,只是从内存中刮擦):

execute block as
declare variable SRC_VAL integer;
declare variable ID_VAL integer;
begin
  for select distinct SOURCE from HISTORY into :SRC_VAL do begin
     :ID_VAL = NULL;
     select first(1) skip(100) ID from HISTORY
       where SOURCE = :SRC_VAL
       order by ID desc
       into :ID_VAL;
     if (:ID_VAL IS NOT NULL) then
       delete from HISTORY 
         where SOURCE = :SRC_VAL 
           and ID <= :ID_VAL;
  end
end