Sql Oracle查询对复杂的删除查询进行优化

Sql Oracle查询对复杂的删除查询进行优化,sql,oracle,oracle11g,query-optimization,sql-delete,Sql,Oracle,Oracle11g,Query Optimization,Sql Delete,我们为Oracle数据库中已有20亿行的表构建了删除查询。此查询是作为PL/SQL过程的一部分执行的。下面是我们目前仍在测试中的查询 DELETE from TABLE1 where ROWID IN (SELECT rid from (SELECT ROWID rid, ROW_NUMBER() over (PARTITION BY C1_Varchar2,C2_Varchar2 ORDER BY C3_Date desc) as Rank

我们为Oracle数据库中已有20亿行的表构建了删除查询。此查询是作为PL/SQL过程的一部分执行的。下面是我们目前仍在测试中的查询

DELETE from TABLE1 
    where ROWID IN (SELECT rid from (SELECT ROWID rid, ROW_NUMBER() over (PARTITION BY C1_Varchar2,C2_Varchar2 ORDER BY C3_Date desc) as Rank 
                                from TABLE1 where C3_Date < ADD_MONTHS(SYSDATE, -20))
                where Rank <> 1);
此查询将从表1中删除除由C1和C2列的唯一组合形成的最新记录外的所有比当前月份早20个月的记录。此查询将删除约12%的记录

当我们运行查询时,我们得到以下错误

ORA-00604:递归SQL级别2发生错误 ORA-04031:无法分配32字节的共享内存共享池,请选择i.obj、i.ts、i.file、…、SQLA、tmp

请注意,该表是根据C3_Date列进行分区的。但是按照上面的逻辑,分区中仍然会保留很少的记录,因此不能选择删除整个分区

有谁能建议如何处理此删除,使其更高效、更稳定

计划如下:

Plan hash value: 2112788339

---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name               | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT             |                    |     1 |    59 |       |  9080K  (2)| 30:16:07 |       |       |
|   1 |  DELETE                      | TABLE1             |       |       |       |            |          |       |       |
|   2 |   NESTED LOOPS               |                    |     1 |    59 |       |  9080K  (2)| 30:16:07 |       |       |
|   3 |    VIEW                      | VW_NSO_1           |   496M|  5684M|       |  6785K  (1)| 22:37:12 |       |       |
|   4 |     SORT UNIQUE              |                    |     1 |    11G|       |            |          |       |       |
|*  5 |      VIEW                    |                    |   496M|    11G|       |  6785K  (1)| 22:37:12 |       |       |
|   6 |       WINDOW SORT            |                    |   496M|    20G|    26G|  6785K  (1)| 22:37:12 |       |       |
|*  7 |        INDEX SKIP SCAN       | XPKTABLE1          |   496M|    20G|       |  1206K  (1)| 04:01:18 |       |       |
|   8 |    TABLE ACCESS BY USER ROWID| TABLE1             |     1 |    47 |       |     1   (0)| 00:00:01 | ROWID | ROWID |
---------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - filter("RANK"<>1)
   7 - access("C3_Date"<ADD_MONTHS(SYSDATE@!,-15))
       filter("C3_Date"<ADD_MONTHS(SYSDATE@!,-15)) 

此查询生成大量临时段26G,如SQL计划和查询中所示,预计运行时间超过30小时。因此,您将在运行时收到ORA-04031

这些是我善意的建议

我建议您不要在大型段的子查询中使用分析函数,因为它们可以防止优化器取消子查询的测试,从而生成运行时视图

第二个建议是附加分区消除谓词,并使用literal或bind变量显式提供日期值。在执行查询之前,在PL/SQL块中预先计算它。不要对条件使用非确定性函数SYSDATE。此外,最好保留较低日期限制的合理条件,这是设计方面的考虑

第三种方法是让Oracle自己查找必须通过使用另一个条件而不是显式提供rowid来删除的行的rowid。在我们的案例中,它可以减少逻辑I/O的数量,使用autotrace进行验证

最后,这个查询可能是这样的,我没有验证它,只是想表达一下想法:

delete from TABLE1 t1_1
 where C3_Date < :upper_date_bound
   and C3_Date >= :lower_date_threshold
   and (C1_Varchar2, C2_Varchar2, C3_Date) not in 
       (select C1_Varchar2, C2_Varchar2, max(C3_Date)
          from table1 t1_2
         where C3_Date < :upper_date_bound
           and C3_Date >= :lower_date_bound
      group by C1_Varchar2, C2_Varchar2)

因为要删除的行数不到表的一半,可以用另一个子查询来考虑IN或En存在子句而不是In。例如,在C3_Date列上创建本地索引,执行统计收集,然后在主查询中尝试这一部分

...
exists (select null from table1 t1_2
         where t1_2.C1_Varchar2 = t1_1.C1_Varchar2
           and t1_2.C2_Varchar2 = t1_1.C2_Varchar2
           and t1_2.C3_Date = t1_1.C3_Date
           /* don't forget about partition selectivity hint */
           and t1_2.C3_Date < :upper_date_bound
           and t1_2.C3_Date >= :lower_date_bound         
         group by t1_2.C1_Varchar2, t1_2.C2_Varchar2
        having t1_1.C3_Date < max(t1_2.C3_Date))  
-


考虑到

考虑将数据分块插入1天,作为一种更有效、更稳定的替代方法

即使在表1中只有12%的月数据也是巨大的,约为2亿

当前状态下的查询也可能导致ORA-01555读取一致性错误。它也可以处理撤销

我还没有测试过这个示例代码,但它会让您了解如何手动为每天创建块1

其想法是在20个月之前获得最大和最小C3_日期。从最小日期导航到最大日期

尝试在“C3_日期”添加一个合适的日期作为前导列,以防止“索引跳过扫描”。这可能也有点帮助。祝你好运

var i_days number ;                             
SELECT (max(C3_Date) - min(C3_Date)) into :i_days  from TABLE1 where C3_Date < ADD_MONTHS(SYSDATE, -20);

-- CHANGE i_mig_start_date IN PROD, if required
var i_mig_start_date varchar2(30); 
exec :i_mig_start_date := ADD_MONTHS(SYSDATE, -20)); 
--exec :i_mig_start_date := '03-OCT-2016 16:00:00';
declare 
    i number;
    v_sql VARCHAR2(4000);
    l_cmd_str VARCHAR2(4000);
    l_from_datetime date;
    l_to_datetime date;
begin
    -- Process chunk  (1 day)
    FOR i IN 1..:i_days LOOP
         l_from_datetime := to_date(:i_mig_start_date,'DD-MON-YYYY HH24:MI:SS')-(i);
         l_to_datetime   := to_date(:i_mig_start_date,'DD-MON-YYYY HH24:MI:SS')-(i-1);

        l_cmd_str := 'DELETE from /*+ PARALLEL(TABLE1  4)*/ TABLE1 
                    where ROWID IN (SELECT rid from (SELECT ROWID rid, ROW_NUMBER() over (PARTITION BY C1_Varchar2,C2_Varchar2 ORDER BY C3_Date desc) as Rank 
                                                from TABLE1 where C3_Date > to_date(:l_from_datetime) and C3_Date <= to_date(:l_to_datetime))
                                    where Rank <> 1
                                    )';
        DBMS_OUTPUT.PUT_LINE('Processing cycle i #'              || i || ' From: ' || l_from_datetime || ' To: ' || l_to_datetime ) ;
        DBMS_OUTPUT.PUT_LINE(l_cmd_str) ;
        execute immediate l_cmd_str using l_from_datetime, l_to_datetime;
        commit;
    END LOOP;    
end;
/

你能为这个问题发布一个解释计划吗?只需解释从表1中删除的计划,其中。。。。。,然后运行SELECT*FROM Table DBMS_XPLAN.Display,然后将结果复制为文本,并将其粘贴到问题中。4031是一个异常错误,可能与查询优化无关。4031意味着严重的内存问题。这些问题可能是由其他进程引起的,而失败的查询并不是问题的真正原因。如果它只是一个测试数据库,而你认为这是一个侥幸,我会重新启动它。我看到查询总是用光临时表空间,但我从来没有看到其中一个生成ORA-4031。我仍然不相信这个问题是关于表现的。我很高兴能得到关于“为什么”否决票的建设性反馈。。。谢谢