繁体   English   中英

Oracle DB:使用自动化进行分割删除

[英]Oracle DB: Portioning delete with Automation

我是使用数据库的新手。 描述以下问题: 问题:有“X”个表和“Y”个分区。 现在想要找到一种方法来查找哪些表具有分区以及哪些分区几乎已满/在需要删除 D-30 之前创建的分区,因为此类问题导致应用程序出错。

请分享指导/询问如何解决此问题?

工件:尝试了几个查询作为 SYSDBA 列出所有分区,但弹出多个依赖项。 仍在尝试找出一种具体的方法来制作调度程序作业。

第 1 步是创建您的分区。 在我的测试用例中,我每周创建 1 个 PARTITION t1= 但我将保留其他分区的语法:

t0 = 每天,t2 = 每月,t3 = 每季度,t4 = 每年。

然后我将使用示例数据填充 PARTITION t1。 我每天使用 1 个日期,但在您的测试期间,每天添加任意数量的日期。 我会建议数据来模拟您的应用程序。

此外,我对表 t1 使用 GLOBAL 索引,而 rest 是 LOCAL 索引。

当全局索引中的一个分区被删除时,必须重建整个索引,否则它将无效。

根据您的应用程序,您需要自行决定是使用全局索引还是本地索引!!

注意:我在 DATE 列上创建所有 PARTITIONS,但如果需要,您也可以使用 TIMESTAMP 列。




/* weekly  PARTITION */

CREATE TABLE t1 (
seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
  PARTITION BY RANGE (dt) 
  INTERVAL ( NUMTODSINTERVAL (7, 'DAY') ) ( 
    PARTITION OLD_DATA VALUES LESS THAN (DATE '2022-04-01')
  );
/

INSERT into t1 (dt)
with dt (dt, interv) as (
select date '2022-04-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-08-31')
select dt from dt;
/

create index t1_global_ix on T1 (dt);
/



/* daily PARTITION */

CREATE TABLE t0 (
seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
  PARTITION BY RANGE (dt) 
  INTERVAL ( NUMTODSINTERVAL (1, 'DAY') ) ( 
    PARTITION OLD_DATA VALUES LESS THAN (DATE 2022-01-01')
  );
/

INSERT into t0 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/

create index t0_global_ix on T0 (dt);
/


/* MONTHLY PARTITION */

CREATE TABLE t2 (
seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
  PARTITION BY RANGE (dt) 
  INTERVAL ( NUMTODSINTERVAL (1, 'MONTH') ) ( 
    PARTITION OLD_DATA VALUES LESS THAN (DATE '2022-01-01')
  );
/

INSERT into t2 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/

create index t2_local_ix on T2 (dt) LOCAL;
/


/* QUARTERLY PARTITION */

CREATE TABLE t3 (     
 seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
   dt   DATE
)
PARTITION BY RANGE (dt)  
INTERVAL 
(NUMTOYMINTERVAL(3,'MONTH'))
(
   PARTITION OLD_DATA values LESS THAN (TO_DATE('2021-01-01','YYYY-MM-DD'))
);
/

INSERT into t3 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/

create index t3_local_ix on T3 (dt) LOCAL;
/

/* yearly PARTITION */

CREATE TABLE t4 (     
 seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
   dt   DATE
)
PARTITION BY RANGE (dt)  
INTERVAL 
(NUMTOYMINTERVAL(1,'YEAR'))
(
   PARTITION OLD_DATA values LESS THAN (TO_DATE('2020-01-01','YYYY-MM-DD'))
);
/

INSERT into t4 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(90,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date 2023-12-31')
select dt from dt;
/

create index t4_local_ix on T4 (dt) LOCAL;
/


每次创建新分区 Oracle 都会生成一个系统名,比较隐晦

这是分区名称的列表,当我加载上面的数据时,生成了 Oracle


SELECT PARTITION_NAME
    FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'


PARTITION_NAME
OLD_DATA
SYS_P458773
SYS_P458774
SYS_P458775
SYS_P458776
SYS_P458777
SYS_P458778
SYS_P458779
SYS_P458780
SYS_P458781
SYS_P458782
SYS_P458783
SYS_P458784
SYS_P458785
SYS_P458786
SYS_P458787
SYS_P458788
SYS_P458789
SYS_P458790
SYS_P458791
SYS_P458792
SYS_P458793
SYS_P458794

尽管 PARTITION 管理将使用系统 GENERATED PARTITION 名称,但我使用以下过程将它们重命名为更有意义的名称。

让我们创建并运行该过程并再次查看名称。 如您所见,由于我们使用的是每周分区,因此名称为 P_ 表示分区 YYYY 分区所在的 4 位数年份,W 表示一年中的第几周,## 表示一年中的第几周。

我建议每天至少使用调度程序运行一次此过程。 您可以根据需要多次运行它,因为它不会造成任何伤害。


CREATE OR REPLACE PROCEDURE MaintainPartitions IS  EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
    PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);

    CURSOR PartTables IS
    SELECT TABLE_NAME, INTERVAL
    FROM USER_PART_TABLES 
    WHERE PARTITIONING_TYPE = 'RANGE' 
    ORDER BY TABLE_NAME;

    CURSOR TabParts(aTableName VARCHAR2) IS 
    SELECT PARTITION_NAME, HIGH_VALUE
    FROM USER_TAB_PARTITIONS
 WHERE regexp_like(partition_name,'^SYS_P[[:digit:]]{1,10}')  AND
 TABLE_NAME = aTableName AND
table_name not like 'BIN$%'
      and    interval is not null
    ORDER BY PARTITION_POSITION;

    ym INTERVAL YEAR TO MONTH;
    ds INTERVAL DAY TO SECOND;
    newPartName VARCHAR2(30);
    PERIOD TIMESTAMP;

BEGIN

    FOR aTab IN PartTables LOOP 
        BEGIN       
            EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ds;
            ym := NULL; 
        EXCEPTION 
            WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
                EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ym;
                ds := NULL;         
        END;            

        FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP         
            EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT PERIOD;
            IF ds IS NOT NULL THEN
                IF ds >= INTERVAL '7' DAY THEN
                    -- Weekly partition
                    EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"IYYY"W"IW';
                ELSE
                    -- Daily partition
                    EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMMDD';
                END IF;
            ELSE
                IF ym = INTERVAL '3' MONTH THEN
                    -- Quarterly partition 
                    EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYY"Q"Q';
                ELSE
                    -- Monthly partition
                    EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMM';
                END IF;
            END IF;

            IF newPartName <> aPart.PARTITION_NAME THEN
                EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newPartName;
            END IF;             
        END LOOP;
    END LOOP;

END MaintainPartitions;
/

EXEC MaintainPartitions

SELECT PARTITION_NAME
    FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'


PARTITION_NAME
OLD_DATA
P_2022W14
P_2022W15
P_2022W16
P_2022W17
P_2022W18
P_2022W19
P_2022W20
P_2022W21
P_2022W22
P_2022W23
P_2022W24
P_2022W25
P_2022W26
P_2022W27
P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34

SELECT COUNT(*) FROM USER_TAB_PARTITIONS 
COUNT(*)
31

下一步是设置 RETENTION 表。 每个区间范围 PARTITION 都应该有一个条目。

RETENTION 值由您决定。 在我的示例中,我选择了 30 天的冷杉表 T1。 这意味着,当 PARTITION 的高值大于 30 天时,它就有资格被删除。 所以在设置这些值时明智地选择。

注意:我列出了其他表的名称,以显示每个表如何具有自己的值。


CREATE TABLE PARTITION_RETENTION (
   seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
    TABLE_NAME VARCHAR2(30),
    RETENTION INTERVAL DAY(3) TO SECOND(0),
 CONSTRAINT
partition_retention_pk primary key (table_name),
CONSTRAINT CHK_NON_ZERO_DAYS CHECK (
        RETENTION > INTERVAL '0' DAY
    ),
    CONSTRAINT CHK_WHOLE_DAYS CHECK (
        EXTRACT(HOUR FROM RETENTION) = 0
        AND EXTRACT(MINUTE FROM RETENTION) = 0
        AND EXTRACT(SECOND FROM RETENTION) = 0
    )
);

insert into PARTITION_RETENTION (TABLE_NAME, RETENTION) 
select 'T0', interval '10' day from dual union all
select 'T1', interval '30' day from dual union all
select 'T2', interval '15' day from dual union all
select 'T3', interval '30' day from dual union all
select 'T4', 15 * interval '1' day from dual union all
select 'T5', 5 * interval '1 00:00:00' day to second from dual;

以下是需要创建的 3 个过程。

ddl 过程是一个包装器,它向您显示正在处理的内容以及需要多长时间。

很明显,rebuild_index 过程会重建任何无效的索引。 正如我上面提到的,如果您使用全局索引并且删除了 PARTITION,则需要重建索引。 我在此示例中对并行 4 进行了硬编码,但如果您有足够的 CPU 能力,您可能希望增加数量以满足您的需求。

此外,还有其他方法可以将索引标记为不可用,因此您可能需要考虑安排该任务。

最后是匿名块。 这实际上删除了保留期已经过去的分区。 这需要每天安排一次!!

如果您仔细查看匿名块,最后一步是调用重建索引过程。 因此,如果索引不可用,它将被重建。

现在让我们运行这个过程,看看会发生什么。



CREATE OR REPLACE PROCEDURE ddl(p_cmd varchar2) 
 authid current_user
is
t1 pls_integer;
BEGIN 
t1 := dbms_utility.get_time; 

dbms_output.put_line(p_cmd);

 execute immediate p_cmd;

dbms_output.put_line((dbms_utility.get_time - t1)/100 || ' seconds');

END;
/

CREATE OR REPLACE PROCEDURE rebuild_index
authid current_user
is

BEGIN 
        for i in (
            select index_owner, index_name, partition_name, 'partition' ddl_type
           from all_ind_partitions
           where status = 'UNUSABLE'
           union all
           select owner, index_name, null, null
           from all_indexes
           where status = 'UNUSABLE'
       )
       loop
         if i.ddl_type is null then
          ddl('alter index '||i.index_owner||'.'||i.index_name||' rebuild parallel 4 online');
         else
          ddl('alter index '||i.index_owner||'.'||i.index_name||' modify '||i.ddl_type||' '||i.partition_name||' rebuild parallel 4 online');
         end if;
       end loop;
 END;
/


DECLARE
    CANNOT_DROP_LAST_PARTITION EXCEPTION;
    PRAGMA EXCEPTION_INIT(CANNOT_DROP_LAST_PARTITION, -14758);

    CANNOT_DROP_ONLY_ONE_PARTITION EXCEPTION;
    PRAGMA EXCEPTION_INIT(CANNOT_DROP_ONLY_ONE_PARTITION, -14083);

   ts TIMESTAMP;
 
   CURSOR TablePartitions IS
    SELECT TABLE_NAME, PARTITION_NAME, p.HIGH_VALUE, t.INTERVAL, RETENTION, DATA_TYPE
    FROM USER_PART_TABLES t
        JOIN USER_TAB_PARTITIONS p USING (TABLE_NAME)
        JOIN USER_PART_KEY_COLUMNS pk ON pk.NAME = TABLE_NAME
        JOIN USER_TAB_COLS tc USING (TABLE_NAME, COLUMN_NAME)
        JOIN PARTITION_RETENTION r USING (TABLE_NAME)
    WHERE        pk.object_type     = 'TABLE' AND
   t.partitioning_type = 'RANGE' AND 
    REGEXP_LIKE (tc.data_type, '^DATE$|^TIMESTAMP.*'); 

BEGIN

    FOR aPart IN TablePartitions LOOP
        EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
        IF ts < SYSTIMESTAMP - aPart.RETENTION THEN
            BEGIN
             ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);
 
            EXCEPTION
                WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
                    DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
  
   ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);
                                                   WHEN CANNOT_DROP_LAST_PARTITION THEN
                    BEGIN
                        DBMS_OUTPUT.PUT_LINE('Drop last partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
                        EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL ()';
 
 ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);

                        EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';            
                    EXCEPTION
                        WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN 
                            -- Depending on the order the "last" partition can be also the "only" partition at the same time
                                                 
                    EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';    

DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
  
         ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);                
               END;
            END;
        END IF;
    END LOOP;
   rebuild_index();
END;

 
alter table T1 drop partition OLD_DATA
.02 seconds
alter table T1 drop partition P_2022W14
.01 seconds
alter table T1 drop partition P_2022W15
.02 seconds
alter table T1 drop partition P_2022W16
.01 seconds
alter table T1 drop partition P_2022W17
.02 seconds
alter table T1 drop partition P_2022W18
.01 seconds
alter table T1 drop partition P_2022W19
.02 seconds
alter table T1 drop partition P_2022W20
.01 seconds
alter table T1 drop partition P_2022W21
.01 seconds
alter table T1 drop partition P_2022W22
.02 seconds
alter table T1 drop partition P_2022W23
.01 seconds
alter table T1 drop partition P_2022W24
.01 seconds
alter table T1 drop partition P_2022W25
.01 seconds
alter table T1 drop partition P_2022W26
.01 seconds
alter table T1 drop partition P_2022W27
.02 seconds
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds
…
…
…
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds


SELECT count(*) from USER_TAB_PARTITIONS 
Where
table_name not like 'BIN$%'

8


SELECT PARTITION_NAME
    FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
AND
table_name not like 'BIN$%'

P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34
P_2022W35

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM