【问题标题】:Delete huge data - truncate partition programmatically删除大量数据 - 以编程方式截断分区
【发布时间】:2016-11-04 20:34:39
【问题描述】:

正如question 中所述,我有下表:

create table prova_log(
       id_dispositive       number,
       type                 number,
       date_verification    date,
       status               number
)
partition by range (date_verification) interval (numtoyminterval(1,'MONTH'))
subpartition by list (type)  
subpartition TEMPLATE (
    SUBPARTITION type1 VALUES (1),
    SUBPARTITION type2 VALUES (2),
    SUBPARTITION type3 VALUES (3),
    SUBPARTITION type4 VALUES (4)
)                          
(
   partition p0816 values less than (to_date('01/09/2016','dd/mm/yyyy'))
);

如您所见,我将第一个分区命名为 p0816。但是下一个分区会随机生成一个名字对吧?

我需要在 oracle 作业中以编程方式清除表。 我在想——因为我的场景是每月处理 1B 行——删除一个分区。但我该怎么做呢?如何按日期范围查找表分区?

我想做这样的事情:

alter table prova_log drop partition XPTO

“XPTO”将是一个随机分区名称,但按日期范围选择 - 假设是 3 个月前。也就是说,删除 2016/08/01 之前的所有内容

【问题讨论】:

    标签: oracle


    【解决方案1】:

    我使用这样的脚本 smt(将表名更改为您的)

    declare
      l_limit_date date := sysdate;
    begin
      for c in (select table_name,
                       partition_name,
                       interval,
                       high_value_in_date_format
                  from (select table_name,
                               partition_name,
                               interval,
                               to_date(trim('''' from regexp_substr(extractvalue(dbms_xmlgen.
                                                                       getxmltype('select high_value from user_tab_partitions where table_name=''' ||
                                                                                  table_name ||
                                                                                  ''' and partition_name = ''' ||
                                                                                  partition_name || ''''),
                                                                       '//text()'),
                                                          '''.*?''')),
                                       'syyyy-mm-dd hh24:mi:ss') high_value_in_date_format
                          from user_tab_partitions
                         where table_name = 'TEST_LOG')
                 where high_value_in_date_format <= l_limit_date
                 order by high_value_in_date_format) 
      loop
        if (c.interval = 'NO') then
          execute immediate 'alter table TEST_LOG truncate partition ' || c.partition_name;
        else
          execute immediate 'alter table TEST_LOG drop partition ' ||  c.partition_name;
        end if;
      end loop;
    end;
    /
    

    【讨论】:

    • 不错。根据环境,可能需要使用 all_tab_partitions 或 dba_tab_partitions。此外,它还没有被提及,但是截断或删除分区将需要重建(或维护)全局索引(至少在 11g 上,在 12c 上不确定)。因此,在截断/删除(慢)时使用“更新全局索引”,或者之后并行重建任何全局索引。
    • @tbone 感谢您的评论。我忘了提到我将此脚本用于没有索引的分区表。我牢记您对未来脚本版本的建议。
    【解决方案2】:

    无需使用随机名称,使用基于日期的可预测分区名称。

    来自文档的示例,命名分区 (Maintaining Partitions):

    ALTER TABLE sales ADD 
      PARTITION sales_q1_2007 VALUES LESS THAN (TO_DATE('01-APR-2007','dd-MON-yyyy')),
      PARTITION sales_q2_2007 VALUES LESS THAN (TO_DATE('01-JUL-2007','dd-MON-yyyy')),
      PARTITION sales_q3_2007 VALUES LESS THAN (TO_DATE('01-OCT-2007','dd-MON-yyyy')),
      PARTITION sales_q4_2007 VALUES LESS THAN (TO_DATE('01-JAN-2008','dd-MON-yyyy'))
    ;
    

    此外,如果您总是删除尾部分区,则不一定需要预先知道名称。可以从数据字典中查询尾部分区(ALL_TAB_PARTITIONSALL_PART_TABLES等)

    我会使用易于记忆和编码的命名方案。使用月份和年份,如果您需要更多的粒度、星期或月份中的某个序号,那么再次使用 LIKE 子句对字典进行查询应该可以满足您的需求。

    【讨论】:

    • 我真的很想使用可预测的分区名称.. 但是 oracle 会为新分区生成一个随机名称,对吧?
    • 不,你可以指定分区名称。
    • 我添加了一个命名分区的示例,参考文档。
    • 根据您的示例,2008 年 1 月 1 日之后的值呢?如果不更改命名新分区的表,oracle 将创建一个具有随机名称的新分区对吗?
    • 由于您使用的是 INTERVAL 分区,Oracle 将为您生成一个。我的例子是传统的方式,自己命名。
    【解决方案3】:

    在 Oracle 中,您有两种方法来寻址分区,它们是等效的。

    ALTER TABLE prova_log DROP PARTITION p0816;
    
    ALTER TABLE prova_log DROP PARTITION FOR ( to_date('01/09/2016','dd/mm/yyyy') );
    

    如果您想处理 SUBPARTITION,语法如下:

    ALTER TABLE prova_log TRUNCATE SUBPARTITION FOR ( to_date('01/09/2016','dd/mm/yyyy'), 2 );
    

    为了删除(或截断)分区,我有这个通用的 PL/SQL 包。最棘手的部分是将HIGH_VALUELONG 数据类型)转换为可用值。

    FUNCTION DailyPartition(tableName IN VARCHAR2) RETURN BOOLEAN IS    
        EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
        PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);    
    
        ds INTERVAL DAY TO SECOND;
        ym INTERVAL YEAR TO MONTH;
        str VARCHAR2(1000);     
    
    BEGIN    
        SELECT INTERVAL INTO str FROM USER_PART_TABLES  WHERE TABLE_NAME = tableName;    
        EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ym;
        RETURN FALSE;    
    EXCEPTION 
        WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
            EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ds;
            RETURN TRUE;    
    END DailyPartition;
    
    
    PROCEDURE DropPartition(tableName IN VARCHAR2, ts IN TIMESTAMP) IS    
        PARTITION_DOES_NOT_EXIST EXCEPTION;
        PRAGMA EXCEPTION_INIT(PARTITION_DOES_NOT_EXIST, -2149);
        sqlstr VARCHAR2(1000);    
    
    BEGIN    
        sqlstr := 'ALTER TABLE '||tableName||' DROP PARTITION FOR (TIMESTAMP '''||TO_CHAR(ts, 'yyyy-mm-dd hh24:mi:ss')||''') UPDATE GLOBAL INDEXES';
        EXECUTE IMMEDIATE sqlstr;    
    EXCEPTION 
        WHEN PARTITION_DOES_NOT_EXIST THEN
            NULL;
    END DropPartition;
    
    
    
    PROCEDURE CleanupPartitions IS
        sqlstr VARCHAR2(10000);
        ts TIMESTAMP;
        tableName VARCHAR2(30) := 'PROVA_LOG';
    
        CURSOR TabPartitions IS
        SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE
        FROM USER_TAB_PARTITIONS 
        WHERE TABLE_NAME = tableName
            AND PARTITION_NAME <> 'P0816' -- Inital partition cannot be dropped
        ORDER BY 1,2;
    
    BEGIN
    
        ts := ADD_MONTHS(TRUNC(LOCALTIMESTAMP) - INTERVAL '1' DAY, -6);
    
        IF DailyPartition(tableName) THEN
            DropPartition(tableName, ts);
            IF EXTRACT(DAY FROM ts) >= 30 THEN
                DropPartition(tab, ts - INTERVAL '1' DAY);
            END IF;
            IF TO_CHAR(ts, 'MM-DD') = '08-31' THEN 
                -- Ensure proper cleanup for February 
                DropPartition(tableName, ts - INTERVAL '2' DAY);
                DropPartition(tableName, ts - INTERVAL '3' DAY);
            END IF;
        ELSE
            DropPartition(tableName, ts);
        END IF;
    
    
    END CleanupPartitions;
    

    保留时间为 6 个月。根据您的需要制作应该没有问题。您甚至可以多次调用程序CleanupPartitions;。通常它会由调度程序作业每月或每天执行一次。

    该软件包适用于 dailymonthly 分区。

    【讨论】:

      猜你喜欢
      • 2013-06-28
      • 2020-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-18
      • 2011-11-14
      相关资源
      最近更新 更多