Oracleで非常に大きなレコードセットを削除する最良の方法


18

私は、非常に大きな(1つのテーブルに5億行を超える1TBに近いデータ)Oracleデータベースバックエンドを持つアプリケーションを管理しています。データベースは実際には何もしません(SProcsもトリガーも何もしません)、それは単なるデータストアです。

毎月、2つのメインテーブルからレコードを削除する必要があります。パージの基準はさまざまで、行の経過時間といくつかのステータスフィールドの組み合わせです。通常、1か月あたり1,000〜5,000万行をパージします(インポートにより、週に約300〜500万行を追加します)。

現在、この削除は約50,000行のバッチで実行する必要があります(つまり、50000の削除、comit、50000の削除、コミット、繰り返し)。バッチ全体を一度にすべて削除しようとすると、データベースが約1時間応答しなくなります(行数によって異なります)。このようなバッチで行を削除することはシステム上で非常に大雑把であり、通常、1週間にわたって「時間の許す限り」それを行う必要があります。スクリプトを継続的に実行できるようにすると、ユーザーが受け入れられないパフォーマンスの低下を招く可能性があります。

この種のバッチ削除もインデックスのパフォーマンスを低下させ、最終的にデータベースのパフォーマンスを低下させる他の影響があると考えています。1つのテーブルに34個のインデックスがあり、インデックスデータのサイズは実際にはデータ自体よりも大きくなっています。

ITスタッフの1人がこのパージを行うために使用するスクリプトを次に示します。

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%増加している必要あり、年に一度だけ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;

パージするレコードの決定方法の背後にあるロジックを過度に単純化したかもしれませんが、これは非常に興味深いアイデアです。ただし、考慮しなければならないことの1つは、日々のパフォーマンスです。パージは「私たちの問題」であり、クライアントはそれを解決するためだけにパフォーマンスの低下を受け入れません。いくつかのコメントとゲイリーの回答から、これはパーティション分割の問題であると思われますか?
コーディングゴリラ

これが私たちが探している答えであるかどうかはわかりませんが、これは間違いなく非常に興味深いアプローチであり、調査します。
コーディングゴリラ

14

これに対する古典的な解決策は、テーブルを月単位または週単位でパーティション分割することです。以前にそれらに遭遇したことがない場合、パーティション化されたテーブルはUNION、選択時に暗黙のいくつかの同一構造のテーブルのようであり、Oracleはパーティション化基準に基づいて挿入するときに適切なパーティションに行を自動的に格納します。あなたはインデックスについて言及します-各パーティションにも独自のパーティションインデックスがあります。パーティションを削除するのは、Oracleでの非常に安価な操作です(これは、TRUNCATEそれはあなたが本当にやっているからです-これらの目に見えないサブテーブルの1つを切り捨てたりドロップしたりするからです。「事後」に分割するのはかなりの量の処理になりますが、こぼれた牛乳で泣いても意味がありません。これまでの利点はコストを上回ります。毎月、トップパーティションを分割して、次の月のデータ用に新しいパーティションを作成します(で簡単に自動化できますDBMS_JOB)。

また、パーティションを使用すると、並列クエリパーティション削除を活用できます。これにより、ユーザーは非常に満足します。


FWIWは、我々は30TB +データベースに自分のサイトでこの手法を使用使用
ガイウス

パーティション化の問題は、データをパーティション化する明確な方法がないことです。2つのテーブルの1つ(以下に示すものではありません)では、パージの実行に使用される基準は、2つの異なる(異なる)日付フィールドとステータスフィールドに基づいています。ステータスがある場合たとえば、Aならば、DateA3歳以上である、それが削除されます。ステータスがある場合BDateB10歳以上である、それはパージ取得します。パーティション化に関する私の理解が正しい場合、このような状況ではパーティション化は有用ではありません(少なくともパージに関する限り)。
コーディングゴリラ

ステータスごとに分割し、日付範囲ごとにサブパーティションを作成できます。ただし、ステータス(または日付)が変更されると、1つのサブパーティションからの削除と、他のサブパーティションへの挿入が効果的に行われます。要するに、あなたはあなたのパージの時間を節約するためにあなたの日常のプロセスに打撃を与えることができます。
ゲイリー

6
または、ステータスがAの場合はDateAを表示し、ステータスがBの場合はDateBを表示し、仮想列でパーティションを表示する仮想列を作成できます。同じパーティション移行が発生しますが、それはパージに役立ちます。これは既に回答として投稿されたようです。
リーリッフェル

4

考慮すべき1つの側面は、インデックスからの削除パフォーマンスの結果と生のテーブルからの削除のパフォーマンスです。テーブルから削除されるすべてのレコードは、すべてのbtreeインデックスから同じ行を削除する必要があります。30以上のbtreeインデックスがある場合、ほとんどの時間はインデックスのメンテナンスに費やされていると思われます。

これは、パーティション化の有用性に影響を与えます。名前にインデックスがあるとします。標準のBtreeインデックスは、すべて1つのセグメントで、ルートブロックからリーフブロックに到達するには4回ジャンプし、行を取得するには5回目の読み取りが必要になる場合があります。そのインデックスが50個のセグメントに分割されており、クエリの一部としてパーティションキーがない場合、それらの50個のセグメントのそれぞれをチェックする必要があります。各セグメントは小さくなるため、2回ジャンプするだけで済みますが、前の5回ではなく100回の読み取りを行うことになります。

ビットマップインデックスの場合、方程式は異なります。おそらく、個々の行を識別するためにインデックスを使用するのではなく、それらのセットを使用します。したがって、5つのIOを使用して単一のレコードを返すクエリではなく、10,000のIOを使用していました。そのため、インデックスの追加パーティションの追加オーバーヘッドは問題になりません。


2

50,000回のバッチで1か月あたり5,000万件のレコードを削除するのは1000回の反復のみです。30分ごとに1削除すると、要件を満たすはずです。投稿したクエリを実行するが、ループを削除して1回だけ実行するようにスケジュールされたタスクは、ユーザーに顕著な劣化を引き起こさないようにします。ほぼ24時間365日稼働する製造工場で、ほぼ同じ量の記録を作成し、ニーズを満たしています。実際には、10分ごとに10,000レコードを少し広げて、Oracle UNIXサーバーで約1〜2秒実行します。


大量の「元に戻す」と「やり直し」「削除」はどうなりますか?それもIOを窒息させます...「削除」ベースのアプローチは確かにNOでなければなりません。大きなテーブルではNOです。
パハリアヨギ

1

ディスク容量が限られている場合は、たとえばmy_table_new、削除するレコードを省略する基準でCTAS(Create Table As Select)を使用して、テーブルの「作業」コピーを作成できます。createステートメントを並行して実行し、appendヒントを使用して高速化してから、すべてのインデックスを構築できます。次に、終了したら(そしてテストして)、既存のテーブルのmy_table_old名前を「work」テーブルの名前に変更し、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)パージが1回限りのタスクである場合に考慮することができます。(b)保持する行が少なく、ほとんどのデータを削除する場合
...-pahariayogi

0

パーティションを削除すると、グローバルインデックスが使用できなくなるため、再構築が必要になります。グローバルインデックスの再構築は大きな問題になります。オンラインで行う場合、非常に遅くなります。いずれの場合も、要件に適合しません。

「通常、1か月あたり1,000万から5,000万行をパージします」

PL / SQLバッチ削除を使用することをお勧めします。数時間は大丈夫だと思います。


1
主キーがある場合、パーティションを削除してもグローバルインデックスが使用できなくなることはありません。ただし、OPに多くのグローバルインデックスがある場合、パーティションの削除に高いコストがかかります。誰かがテーブルをパーティション分割する理想的なケースでは、パーティション分割は主キーに基づいており、グローバルインデックスは必要ありません。すべてのクエリがパーティションプルーニングを利用できること。
Gandolf989 14

@ Gandolf989パーティションを削除すると、常にグローバルインデックスが使用できなくなります
miracle173
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.