web-dev-qa-db-ja.com

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

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

毎月、2つのメインテーブルからレコードを削除する必要があります。パージの基準はさまざまで、行の経過時間といくつかのステータスフィールドの組み合わせです。通常、1か月あたり1,000万〜5,000万行をパージします(インポートにより、1週間で約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日間のメンテナンス期間は年に1回しかありません。

これらのレコードを削除するためのより良い方法を探していますが、まだ見つける方法はありません。助言がありますか?

19
Coding Gorilla

'A'と 'B'のロジックは、パーティショニングを実行できるvirtual列の後ろに「隠されている」可能性があります。

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;
18

これに対する古典的な解決策は、テーブルを partition にすることです。月ごとまたは週ごと。これまでにそれらに遭遇したことがない場合、パーティション化されたテーブルは、選択時に暗黙的なUNIONを持ついくつかの同じ構造のテーブルのようなものであり、Oracleはパーティション化基準に基づいて行を挿入するときに、適切なパーティションに行を自動的に格納します。あなたはインデックスについて言及します-まあ、各パーティションは独自のパーティション化されたインデックスも取得します。パーティションを削除することは、Oracleで非常に安価な操作です(これは、実際には、これらの非表示のサブテーブルの1つを切り捨てたり削除したりするため、負荷の点ではTRUNCATEに似ています)。 「事後」に分割することはかなりの量の処理になりますが、こぼれた牛乳を叫ぶことには意味がありません。これを行うことの利点はコストを上回ります。毎月、上のパーティションを分割して、翌月のデータ用の新しいパーティションを作成します(DBMS_JOBを使用すると、簡単に自動化できます)。

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

14
Gaius

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

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

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

4
Gary

50,000のバッチで1か月に5,000万件のレコードを削除しても、反復はわずか1000回です。 30分ごとに1つ削除すると、要件を満たすはずです。投稿したクエリを実行するが、ループを削除して1回だけ実行するようにスケジュールされたタスクは、ユーザーに顕著な低下を引き起こすべきではありません。 24時間年中無休で稼働する製造工場では、ほぼ同じ量のレコードを処理しており、それは私たちのニーズを満たしています。実際には、10分ごとに10,000レコードを少し広げて、Oracle unixサーバーで約1秒または2秒で実行します。

2
Jason Jakob

ディスク容量が不足していない場合は、CTAS(Create Table As Select)を使用して、削除するレコードを省略できる基準を使用して、テーブルの「作業」コピー、たとえばmy_table_newを作成できます。 createステートメントを並列に実行し、appendヒントを使用して高速化してから、すべてのインデックスを構築できます。次に、終了して(そしてテストして)、既存のテーブルの名前をmy_table_oldに変更し、「作業」テーブルの名前をmy_tableに変更します。すべてに慣れたらdrop my_table_old purgeで古いテーブルを削除します。外部キーの制約がたくさんある場合は、dbms_redefinitionPL/SQLパッケージ を確認してください。適切なオプションを使用すると、インデックスや制約などのクローンが作成されます。これは、Tom Kyteによる AskTom 名声の提案をまとめたものです。最初の実行後、すべてを自動化できます。テーブルの作成ははるかに迅速に行われ、システムの稼働中に実行できます。アプリケーションのダウンタイムは、テーブルの名前変更を行うまで1分未満に制限されます。 CTASを使用すると、複数のバッチ削除を実行するよりもはるかに高速になります。このアプローチは、パーティション分割のライセンスがない場合に特に役立ちます。

CTASの例、過去365日間のデータと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
Mark Stewart

パーティションを削除すると、グローバルインデックスが使用できなくなり、再構築が必要になります。グローバルインデックスの再構築は大きな問題になります。オンラインで行うと非常に遅くなり、そうでなければダウンタイムが必要になります。どちらの場合も、要件に適合できません。

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

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

0
iceburge5