One of my customers had a problem with a very slow TRUNCATE TABLE operation on a partitioned table having 22 000 partitions. The problem has raised after a test migration to database 12c. I’ve prepared a little demo to address this issue and show the walkaround for it.
Oracle Database 12c (12.1.0.2):
[oracle@skiper ~]$ sqlplus hr/hr
SQL*Plus: Release 12.1.0.2.0 Production on Tue Jan 27 10:11:23 2015
Copyright (c) 1982, 2014, Oracle. All rights reserved.
Last Successful login time: Mon Jan 26 2015 17:56:53 +01:00
Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, Automatic Storage Management, OLAP, Advanced Analytics
and Real Application Testing options
SQL> select count(1)
2 from user_tab_partitions
3 where table_name='TRUNCATE_TEST';
COUNT(1)
----------
22000
SQL> set timing on
SQL> truncate table TRUNCATE_TEST;
Table truncated.
Elapsed: 00:05:33.71
Oracle Database 11g (11.2.0.4)
[oracle@skiper ~]$ sqlplus hr/hr
SQL*Plus: Release 11.2.0.4.0 Production on Tue Jan 27 10:15:05 2015
Copyright (c) 1982, 2013, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, Automatic Storage Management, OLAP, Data Mining
and Real Application Testing options
SQL> select count(1)
2 from user_tab_partitions
3 where table_name='TRUNCATE_TEST';
COUNT(1)
----------
22000
SQL> set timing on
SQL> truncate table TRUNCATE_TEST;
Table truncated.
Elapsed: 00:00:06.16
What the hell? On 12c the truncate took 00:05:33.71 and on 11g it was only 00:00:06.16
To understand the problem I’ve used the 10046 event on level 12 to generate trace with bind variables and wait events:
SQL> get spid 1 select spid 2 from v$process p, v$session s 3 where p.addr=s.paddr 4* and s.username='HR' SQL> / SPID ------------------------ 4797 SQL> oradebug setospid 4797 Oracle pid: 28, Unix process pid: 4797, image: oracle@skiper (TNS V1-V3) SQL> oradebug event 10046 trace name context forever, level 12 Statement processed. SQL> oradebug tracefile_name /u01/app/oracle/diag/rdbms/rico/rico/trace/rico_ora_4797.trc
A quick look on the size of the trace files assures me that in 12c there is a lot more operations during the "TRUNCATE TABLE".
[oracle@skiper truncate_test]$ wc -l raw_truncate11g.trc 1113 raw_truncate11g.trc [oracle@skiper truncate_test]$ wc -l raw_truncate12c.trc 4313645 raw_truncate12c.trc
Further investigations shows the SQLs which are the reason of a slow TRUNCATE execution in the 12c database.
select inserts, deletes from sys.mon_mods$ where obj# = :objn call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 44000 2.18 2.70 0 0 0 0 Execute 44000 8.56 9.89 0 0 0 0 Fetch 44000 3.14 4.03 0 110000 0 22000 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 132000 13.88 16.62 0 110000 0 22000
SQL ID: bb926a5dcb8kr Plan Hash: 3530933756
merge into sys.mon_mods$ m
using dual
on (m.obj# = :objn and
(:pobjn = 0 or
m.obj# = :pobjn or
m.obj# in (select obj#
from tabpart$
where bo# = :pobjn and obj# = :objn
union all
select obj#
from tabcompart$
where bo# = :pobjn and obj# = :objn
union all
select tsp.obj#
from tabsubpart$ tsp, tabcompart$ tcp
where tsp.pobj# = tcp.obj#
and (tcp.bo# = :pobjn or tcp.obj# = :pobjn)
and tsp.obj# = :objn))) when matched
then update
set inserts =
inserts + :ins, updates = updates + :upd, deletes =
deletes + :del, flags =
(decode(bitand(flags, :flag), :flag, flags, flags + :flag)),
drop_segments = drop_segments + :dropseg,
timestamp = :time when
NOT matched then
insert (obj#, inserts, updates, deletes,
timestamp, flags, drop_segments)
values (:objn, :ins, :upd, :del, :time, :flag, :dropseg)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 44000 2.01 2.28 0 0 0 0
Execute 44000 57.61 65.34 0 264100 133393 44000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 88000 59.62 67.63 0 264100 133393 44000
The MON_MODS$ table is related to the following views:
- USER_TAB_MODIFICATIONS
- ALL_TAB_MODIFICATIONS
- DBA_TAB_MODIFICATIONS
- ALL_TAB_STATISTICS
- DBA_TAB_STATISTICS
- USER_TAB_STATISTICS
- ALL_IND_STATISTICS
- DBA_IND_STATISTICS
- USER_IND_STATISTICS
- _user_stat
So it looks like that this behavior is related to the DML monitoring feature. Is it possible? The "TRUNCATE TABLE" is a DDL, not a DML operation!
Let’s check it by turning off the DML monitoring feature:
[oracle@skiper ~]$ sqlplus / as sysdba SQL*Plus: Release 12.1.0.2.0 Production on Tue Jan 27 11:08:03 2015 Copyright (c) 1982, 2014, Oracle. All rights reserved. Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production With the Partitioning, Automatic Storage Management, OLAP, Advanced Analytics and Real Application Testing options SQL> alter system set "_dml_monitoring_enabled"=FALSE scope=memory; System altered. SQL> conn hr/hr Connected. SQL> set timing on SQL> truncate table TRUNCATE_TEST; Table truncated. Elapsed: 00:00:00.18
WOW! So the conclusion is: TRUNCATE TABLE in database 12c is using the DML monitoring feature for some reason! Why? I don’t know it yet but I have a hypothesis that this is somehow related to the new features in the database 12c, regarding online operations on partitioned tables:
http://docs.oracle.com/database/121/VLDBG/to_vldbg14107_d290.htm#VLDBG14107
The problem is: my test table has no indexes and no data. It’s just an empty table with 22000 partitions. And database knows it, because I’ve gathered statistics:
SQL> select last_analyzed, num_rows 2 from user_tables 3 where table_name='TRUNCATE_TEST'; LAST_ANAL NUM_ROWS --------- ---------- 26-JAN-15 0 SQL> select sum(num_rows), sum(blocks) 2 from user_tab_partitions 3 where table_name='TRUNCATE_TEST'; SUM(NUM_ROWS) SUM(BLOCKS) ------------- ----------- 0 0
So what to do? As always: keep calm and use hidden parameters 🙂
