Oracle에서 매우 큰 레코드 세트를 삭제하는 가장 좋은 방법


18

Oracle 데이터베이스 백엔드가 매우 큰 (한 테이블에 5 억 개 이상의 행이있는 거의 1TB의 데이터) 응용 프로그램을 관리합니다. 데이터베이스는 실제로 아무 것도하지 않습니다 (SProcs, 트리거 또는 아무것도하지 않음)는 데이터 저장소 일뿐입니다.

매달 우리는 두 개의 기본 테이블에서 레코드를 제거해야합니다. 제거 기준은 다양하며 행 연령과 몇 가지 상태 필드의 조합입니다. 일반적으로 한 달에 1 천 -5 천만 행을 제거합니다 (수입을 통해 일주일에 약 3-5 백만 행 추가).

현재 우리는이 삭제를 약 50,000 개의 행으로 일괄 처리해야합니다 (즉, 50000 삭제, 커밋, 50000 삭제, 커밋, 반복). 전체 배치를 한 번에 모두 삭제하려고하면 행 수에 따라 약 1 시간 동안 데이터베이스가 응답하지 않습니다. 이와 같이 배치에서 행을 삭제하는 것은 시스템에서 매우 거칠고 일주일 동안 "시간이 허락하는대로"행을 수행해야합니다. 스크립트를 계속 실행하면 성능이 저하 될 수 있습니다.

이런 종류의 배치 삭제는 인덱스 성능을 저하시키고 데이터베이스 성능을 저하시키는 다른 영향을 미칩니다. 하나의 테이블에 34 개의 인덱스가 있으며 인덱스 데이터 크기는 실제로 데이터 자체보다 큽니다.

IT 직원 중 한 명이이 제거를 위해 사용하는 스크립트는 다음과 같습니다.

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

이 데이터베이스 99.99999 % 이상 이어야 하며 1 년에 한 번만 2 일 유지 관리 기간이 있습니다.

이 레코드를 제거하는 더 좋은 방법을 찾고 있지만 아직 찾지 못했습니다. 어떤 제안?


또한 여기에는 30 개 이상의 인덱스가 있습니다
jcolebrand

답변:


17

'A'및 'B'가있는 논리 는 분할을 수행 할 수 있는 가상 열 뒤에 "숨겨 질"수 있습니다 .

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;

제거 할 레코드를 결정하는 방식에 대한 논리를 지나치게 단순화했을 수도 있지만 이는 매우 흥미로운 아이디어입니다. 그러나 고려해야 할 사항은 일상적인 성능입니다. 제거는 "우리의 문제"이며 클라이언트는이를 해결하기 위해 성능 저하를 허용하지 않습니다. 일부 의견과 Gary의 답변에서 이것이 파티셔닝에 문제가 될 수 있다고 들었습니다.
코딩 고릴라

이것이 우리가 찾고 있는 대답 인지 확실하지 않지만, 이것은 우리가 조사 할 매우 흥미로운 접근법입니다.
코딩 고릴라

14

이에 대한 전형적인 해결책은 예를 들어 월 또는 주 단위로 테이블 을 분할하는 것입니다. 이전에이 테이블을 보지 않았다면, 분할 된 테이블은 UNION선택할 때 암시 적으로 동일한 구조화 된 여러 테이블과 같 으며, 분할 기준에 따라 삽입 할 때 Oracle은 자동으로 해당 파티션에 행을 저장합니다. 인덱스를 언급하면 ​​각 파티션마다 고유 한 파티션 된 인덱스를 얻게됩니다. 오라클은 파티션을 삭제하는 것이 매우 저렴한 작업입니다.TRUNCATE그것이 보이지 않는 하위 테이블 중 하나를 자르거나 삭제하는 것이 실제로 수행중인 작업이기 때문에로드 측면에서). "사실 이후"를 분할하는 것은 상당한 양의 처리가 될 것이지만, 유출 된 우유에 대한 울음은 의미가 없습니다. 매월 맨 위 파티션을 분할하여 다음 달 데이터에 대한 새 파티션을 만듭니다 (를 사용하여 쉽게 자동화 할 수 있음 DBMS_JOB).

또한 파티션을 사용하면 병렬 쿼리파티션 제거를 활용할 수 있으므로 사용자가 매우 행복해집니다 ...


FWIW 우리는 내 사이트에서 30Tb + 데이터베이스의이 기술을 사용합니다
Gaius

파티셔닝의 문제점은 데이터를 파티셔닝하는 명확한 방법이 없다는 것입니다. 제거를 수행하는 데 사용되는 두 테이블 중 하나 (아래 표시된 테이블이 아님)에서 두 개의 다른 (고유 한) 날짜 필드와 상태 필드를 기반으로합니다. 예를 들어, 상태 인 경우 A다음 경우 DateA이를 제거됩니다, 나이가 3 년 이상이다. 상태 인 경우 BDateB나이가 10 년 이상, 그것은이 제거됩니다. 파티셔닝에 대한 나의 이해가 정확하다면, 파티셔닝은 이런 상황에서 (적어도 퍼지에 관한 한) 유용하지 않을 것입니다.
코딩 Gorilla

날짜 범위별로 상태 및 하위 파티션별로 파티션을 나눌 수 있습니다. 그러나 상태 (또는 날짜)가 변경되면 한 하위 파티션에서 삭제하고 다른 하위 파티션으로 효과적으로 삽입합니다. 간단히 말해서, 퍼지 시간을 절약하기 위해 일상적인 프로세스에 영향을 줄 수 있습니다.
게리

6
또는 상태가 A 인 경우 DateA를 표시하고 상태가 B 인 경우 DateB를 표시하는 가상 컬럼을 작성한 후 가상 컬럼에서 파티션을 작성할 수 있습니다. 동일한 파티션 마이그레이션이 발생하지만 제거에 도움이됩니다. 이미 답변으로 게시 된 것 같습니다.
레이 리펠

4

고려해야 할 한 가지 측면은 인덱스의 삭제 성능 결과와 원시 테이블의 결과입니다. 테이블에서 삭제 된 모든 레코드는 모든 btree 인덱스에서 동일한 행 삭제가 필요합니다. btree 인덱스가 30 개 이상인 경우 대부분의 시간이 인덱스 유지 관리에 소비되는 것 같습니다.

이는 파티셔닝의 유용성에 영향을 미칩니다. 이름에 색인이 있다고 가정하십시오. 하나의 세그먼트에있는 표준 Btree 인덱스는 루트 블록에서 리프 블록으로 이동하기 위해 네 번의 점프를 수행하고 행을 얻기 위해 다섯 번째로 읽어야 할 수도 있습니다. 해당 인덱스가 50 개의 세그먼트로 분할되어 있고 쿼리의 일부로 파티션 키가없는 경우 해당 50 개의 세그먼트 각각을 확인해야합니다. 각 세그먼트는 더 작으므로 2 번의 점프 만하면되지만 이전 5 번이 아닌 100 번의 읽기를 계속할 수 있습니다.

비트 맵 인덱스 인 경우 방정식이 다릅니다. 개별 행을 식별하기 위해 인덱스를 사용하지 않고 오히려 행을 설정하고있을 것입니다. 따라서 5 개의 IO를 사용하여 단일 레코드를 반환하는 쿼리가 아니라 10,000 개의 IO를 사용했습니다. 따라서 인덱스에 대한 추가 파티션의 추가 오버 헤드는 중요하지 않습니다.


2

50,000 개의 배치에서 한 달에 5 천만 개의 레코드를 삭제하는 것은 1000 회 반복입니다. 30 분마다 1 번 삭제하면 요구 사항을 충족해야합니다. 게시 한 쿼리를 실행하지만 루프를 제거하여 예약 된 작업이 한 번만 실행되므로 사용자에게 눈에 띄는 탈지가 발생하지 않아야합니다. 우리는 제조 공장에서 거의 24/7에 달하는 거의 동일한 양의 레코드를 수행하며 우리의 요구를 충족시킵니다. 실제로 10 분마다 10,000 개 이상의 레코드를 배포하여 Oracle unix 서버에서 약 1 ~ 2 초 동안 실행됩니다.


대규모 '실행 취소'및 '재실행' '삭제'는 어떻게 생성됩니까? IO를 질식시킵니다 ... '삭제'기반 접근 방식은 큰 테이블의 경우 NO .. NO이어야합니다.
pahariayogi

1

디스크 공간이 my_table_new부족하면 CTAS (Create Table As Select)를 사용하여 레코드 삭제를 생략하는 기준을 사용하여 테이블의 "작업"사본을 작성할 수 있습니다 . create 문을 병렬로 추가하고 힌트를 추가하여 빠르게 만들고 모든 인덱스를 작성할 수 있습니다. 그런 다음 완료하고 테스트 한 후에는 기존 테이블의 my_table_old이름을 바꾸고 "작업"테이블의 이름 을 로 바꿉니다 my_table. drop my_table_old purge이전 테이블을 제거하기 위해 모든 것에 익숙해지면 . 외래 키 제약이 dbms_redefinition 많으면 PL / SQL 패키지를 살펴보십시오 . 적절한 옵션을 사용할 때 색인, 제약 조건 등을 복제합니다. 이것은 AskTom의 Tom Kyte의 제안을 요약 한 것입니다.명성. 처음 실행 한 후에는 모든 것을 자동화 할 수 있으며 테이블 작성이 훨씬 빨라지고 시스템이 가동되는 동안 수행 될 수 있으며 애플리케이션 가동 중지 시간은 테이블 이름 바꾸기를 수행하는 데 1 분 미만으로 제한됩니다. CTAS를 사용하면 여러 배치 삭제를 수행하는 것보다 훨씬 빠릅니다. 이 방법은 파티셔닝 라이센스가없는 경우 특히 유용합니다.

지난 365일으로부터 데이터 행을 유지 CTAS 샘플 flag_inactive = 'N':

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;

1
(a) 제거가 일회성 작업 인 경우 고려할 수 있습니다. (b) 유지해야 할 행 수와 대부분의 데이터를 제거 할 경우 ...
pahariayogi

0

파티션을 삭제할 때 글로벌 인덱스를 사용할 수없는 상태로두면 다시 작성해야합니다. 글로벌 인덱스의 재 구축은 마치 온라인에서하는 것처럼 상당히 느릴 것입니다. 그렇지 않으면 다운 타임이 필요합니다. 두 경우 모두 요구 사항에 맞지 않습니다.

"우리는 일반적으로 매월 1 천만에서 5 천만 행을 제거합니다"

PL / SQL 일괄 삭제를 사용하는 것이 좋으며 몇 시간은 괜찮습니다.


1
기본 키가있는 경우 파티션을 삭제하면 글로벌 인덱스를 사용할 수 없어야합니다. 그러나 OP에 글로벌 인덱스가 많으면 파티션을 삭제하는 데 비용이 많이 듭니다. 이상적인 경우 누군가가 테이블을 분할하는 경우 분할은 기본 키를 기반으로하며 글로벌 인덱스가 필요하지 않습니다. 모든 쿼리는 파티션 정리를 활용할 수 있습니다.
Gandolf989

@ Gandolf989 파티션을 삭제하면 항상 글로벌 인덱스를 사용할 수 없게됩니다
miracle173
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.