【问题标题】:Massive Delete statement - How to improve query execution time?海量删除语句 - 如何提高查询执行时间?
【发布时间】:2020-12-27 00:02:23
【问题描述】:

我有一个 Spring 批处理,每天都会运行到:

  1. 读取 CSV 文件并将其导入我们的数据库

  2. 聚合这些数据并将这些聚合数据保存到另一个表中。

我们有一个表BATCH_LIST,其中包含有关已执行的所有批次的信息。

BATCH_LIST 具有以下列:

 1. BATCH_ID
 2. EXECUTION_DATE
 3. STATUS

在导入的 CSV 文件中,我们有一个 CSV 文件用于提供APP_USERS 表,另一个用于提供ACCOUNTS 表。

APP_USERS 具有以下列:

 1. USER_ID
 2. BATCH_ID
 -- more columns

ACCOUNTS 具有以下列:

 1. ACCOUNT_ID
 2. BATCH_ID
 -- more columns

在第 2 步中,我们聚合来自 ACCOUNTSAPP_USERS 的数据,以将行插入到 USER_ACCOUNT_RELATION 表中。该表正好有两列:ACCOUNT_ID(参考ACCOUNTS.ACCOUNT_ID)和USER_ID(参考APP_USERS.USER_ID)。

现在我们想在 Spring 批处理中添加另一个步骤。我们要删除USER_ACCOUNT_RELATION 表中的所有数据,以及不再相关的APP_USERSACCOUNTS(即在sysdate - 2 之前导入的数据。

到目前为止做了什么:

  1. 获取我们要从数据库中删除的所有BATCH_ID

    SELECT BATCH_ID FROM BATCH_LIST WHERE trunc(EXECUTION_DATE) < sysdate - 2
    
  2. 对于每个BATCH_ID,我们调用以下方法:

     public void deleteAppUsersByBatchId(Connection connection, long batchId) throws SQLException  
      // prepared statements to delete User account relation and user
    
    

这是两个准备好的语句:

DELETE FROM USER_ACCOUNT_RELATION 
WHERE USER_ID IN (
   SELECT USER_ID FROM APP_USERS WHERE BATCH_ID = ?
);
DELETE FROM APP_USERS WHERE BATCH_ID = ?

我的问题是删除一个 BATCH_ID 的数据需要很长时间(超过 1 小时)。

注意:我只提到了APP_USERSACCOUNTSUSER_ACCOUNT_RELATION 表,但实际上我有大约 25 个表要删除。

如何提高查询时间? (我刚刚尝试将WHERE USER_ID IN () 更改为EXISTS。它更好但仍然太长了。

【问题讨论】:

  • 你有多少个XX?你从哪里得到它们?
  • 现在,我有 70 个。每天,我们都会收到导入数据库的文件。在生产环境中,我不会像在测试环境中那样拥有那么多的 FILE_ID。
  • 在 FILE_ID 上为 USER 和 ACCOUNT 添加索引。
  • 非常感谢。一开始我想这样做(这就是我在索引上添加注释的原因),但我在某处读到,你拥有的索引越多,删除的速度就越慢。
  • 你有分区选项吗?删除数据最简单的方法是删除整个分区

标签: sql database oracle performance


【解决方案1】:

如果这将是您的常规流程,即您只想存储最后 2 天,则不需要索引,因为每次您都会删除所有行的 1/3。

最好只使用 3 次删除而不是 3*7 次单独删除:

DELETE FROM USER_ACCOUNT_RELATION 
WHERE ACCOUNT_ID IN
(
  SELECT u.ID 
  FROM {USER} u 
  join {FILE} f
       on u.FILE_ID = f.file
  WHERE trunc(f.IMPORT_DATE) < (sysdate - 2)
);

DELETE FROM {USER}
  WHERE FILE_ID in (select FILE_ID from {file} where trunc(IMPORT_DATE) < (sysdate - 2));
  
DELETE FROM {ACCOUNT}
  WHERE FILE_ID in (select FILE_ID from {file} where trunc(IMPORT_DATE) < (sysdate - 2));

只需将{USER}{FILE}{ACCOUNT} 替换为您的真实表名即可。

显然,在分区选项的情况下,它会更容易 - 每日间隔分区,因此您可以轻松删除旧分区。

但即使在您的情况下,还有另一个更困难但非常快速的解决方案 - “分区视图”:例如对于 ACCOUNT,您可以创建 3 个不同的表 ACCOUNT_1、ACCOUNT_2 和 ACCOUNT_3,然后创建分区视图:

create view ACCOUNT as
select 1 table_id, a1.* from ACCOUNT_1 a1
union all
select 2 table_id, a2.* from ACCOUNT_2 a2
union all
select 3 table_id, a3.* from ACCOUNT_3 a3;

然后您可以在此视图上使用而不是触发器将每日数据插入到自己的表中:第一天到 account_1,第二个 - account_2 等。并在每个午夜截断旧表。您可以使用

轻松获取表名
select 'ACCOUNT_'|| (mod(to_char(sysdate, 'j'),3)+1) tab_name from dual;

【讨论】:

  • 谢谢。我刚刚尝试了子查询解决方案,但我得到了 ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired 错误。将表格分成 3 份的想法也很有趣。但是,如果我需要将天数从 2 更改为另一个数字,您的建议是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多