我们在Oracle数据库中为一个表(已经有20亿行)构建了一个删除查询。此查询作为PL / SQL Proc的一部分执行。以下是我们当前仍在测试中的查询。
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);
此查询将删除从当前月起超过20个月的所有记录(来自TABLE1),但由C1和C2列的唯一组合形成的最新记录除外。此查询将删除大约12%的记录。
当我们运行查询时,我们得到以下错误。
ORA-00604:递归SQL级别2发生错误 ORA-04031:无法分配32字节的共享内存(&#34;共享池&#34;,&#34;选择i.obj#,i.ts#,i.file#,...&#34; &#34; SQLA&#34;&#34; TMP&#34)
请注意,该表是基于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))
答案 0 :(得分:0)
此查询生成大量临时段(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”或“EXISTS”子句与另一个子查询一起考虑,而不是“NOT 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)
考虑插入块(数据为1天)作为一种非常有效和稳定的替代方案。
即使在TABLE1中只有12%的月份数据是巨大的(大约200万)!
当前状态下的查询也可能导致ORA-01555(读取一致性)错误。它也将处理UNDO。
我没有测试过此示例代码,但它会让您了解如何手动创建块(每天1个)。
这个想法是在20个月之前获得最大和最小C3_Date。从最小日期导航到最大日期。
尝试添加合适的'C3_Date'作为前导列以防止'INDEX SKIP SCAN'。这可能有点帮助。祝好运!
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;
/