Oracleで非常に大きなレコードセットを削除する最良の方法
-
16-10-2019 - |
質問
Oracle Databaseのバックエンドで、非常に大きなデータ(1つのテーブルに5億行以上を超える1TBのデータ)を備えたアプリケーションを管理しています。データベースは実際には何もしません(スプロックもトリガーも何もありません)それは単なるデータストアです。
毎月、メインテーブルの2つからレコードをパージする必要があります。パージの基準はさまざまであり、列時代といくつかのステータスフィールドの組み合わせです。通常、1か月あたり1,000万から5,000万行の列をパージすることになります(輸入を介して週に約3〜500万行を追加します)。
現在、約50,000行のバッチでこれを削除する必要があります(つまり、50000を削除し、50000を削除し、コミット、繰り返し削除します)。一度にバッチ全体をすべて削除しようとすると、データベースは約1時間(行の#に応じて)反応しません。このようなバッチで行を削除することは、システム上で非常に荒いものであり、通常、1週間にわたって「時間が許す限り」を行う必要があります。スクリプトを継続的に実行できるようにすると、ユーザーが受け入れられないパフォーマンスの劣化が生じる可能性があります。
この種のバッチ削除は、インデックスのパフォーマンスを低下させ、最終的にデータベースのパフォーマンスを劣化させる他の影響を及ぼしていると思います。 1つのテーブルには34のインデックスがあり、インデックスデータサイズは実際にはデータ自体よりも大きくなっています。
ITがこのパージを行うために使用する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%を上げて、年に1回しか2日間のメンテナンスウィンドウがあります。
これらのレコードを削除するためのより良い方法を探していますが、まだ見つかりません。助言がありますか?
解決
「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;
他のヒント
これに対する古典的な解決策は次のとおりです パーティション たとえば、月または週ごとにテーブル。以前にそれらに出くわしたことがない場合、パーティションテーブルは、暗黙的ないくつかの同一に構造化されたテーブルのようなものです UNION
選択するとき、Oracleは、パーティション基準に基づいて挿入するときに適切なパーティションに行を自動的に保存します。インデックスに言及します - 各パーティションも独自のパーティション化されたインデックスを取得します。パーティションをドロップするのは非常に安価な操作です(それはに類似しています TRUNCATE
それがあなたが本当にしていることであるため、負荷に関しては、これらの目に見えないサブテーブルの1つを切り捨てたりドロップしたりします)。 「事実後」の後に分割するためのかなりの量の処理になりますが、こぼれた牛乳で泣いている意味はありません。毎月、トップパーティションを分割して、来月のデータの新しいパーティションを作成します(THSを簡単に自動化できます。 DBMS_JOB
).
パーティションを使用すると、悪用することもできます 並列クエリ と パーティションの除去, 、ユーザーがとても幸せになるはずです...
考慮すべき側面の1つは、削除性能がインデックスからどれだけ生じるか、生のテーブルからどれだけ生じるかです。テーブルから削除されたすべてのレコードには、すべてのBtreeインデックスから行が同じ削除されます。 30以上のBtreeインデックスがある場合、ほとんどの時間はインデックスメンテナンスに費やされていると思います。
これは、パーティション化の有用性に影響を与えます。名前にインデックスがあるとします。すべてのセグメントにある標準のBtreeインデックスは、ルートブロックからリーフブロックに到達するために4つのジャンプを行う必要がある場合があり、5番目の読み取りは行を取得する必要があります。そのインデックスが50のセグメントに分割され、クエリの一部としてパーティションキーがない場合、これらの50のセグメントのそれぞれをチェックする必要があります。各セグメントは小さくなるため、2つのジャンプしか行う必要がありますが、前の5つではなく100の読み取りを行うことになります。
それらがビットマップインデックスである場合、方程式は異なります。おそらく、個々の行を識別するためにインデックスを使用しているのではなく、それらのセットを識別するのではありません。したがって、5 iOSを使用して単一のレコードを返すクエリではなく、10,000 iOSを使用していました。そのため、インデックスの追加パーティションの追加オーバーヘッドは重要ではありません。
50,000のバッチでの月額5,000万件の記録の削除は、1000回の反復です。 30分ごとに1を削除すると、要件を満たす必要があります。投稿したクエリを実行するためのスケジュールされたタスクですが、1回だけ実行するようにループを削除してください。私たちは、製造工場でほぼ同じ量のレコードを実行し、24時間年中無休で運営しており、ニーズを満たしています。実際には、10分ごとに10,000のレコードをもう少し広げます。これは、Oracle Unixサーバーで約1〜2秒で実行されます。
ディスクスペースがプレミアムにない場合は、テーブルの「作業」コピーを作成できる可能性があります。 my_table_new
, 、削除するレコードを省略する基準を備えたCTA(選択としてテーブルを作成)を使用します。 Createステートメントを並行して実行し、追加のヒントを使用して高速にすることができ、すべてのインデックスを構築できます。その後、終了したら(およびテスト)、既存のテーブルの名前を次に変更します my_table_old
「作業」テーブルの名前を変更します my_table
. 。すべてに満足したら drop my_table_old purge
古いテーブルを取り除くには。外国のキーの拘束がたくさんある場合は、 dbms_redefinition
PL/SQLパッケージ. 。適切なオプションを使用すると、インデックス、コントレントなどがクローン化されます。これは、Tom Kyteによる提案の要約です AskTom 名声。最初の実行の後、すべてを自動化でき、作成テーブルははるかに速くなり、システムが上がっている間に実行することができ、アプリケーションのダウンタイムはテーブルの名前変更を行うまで1分未満に制限されます。 CTAを使用すると、いくつかのバッチ削除を実行するよりもはるかに高速になります。このアプローチは、ライセンスをパーティション化していない場合に特に役立ちます。
CTAをサンプリングし、過去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か月あたり1,000万列の間でパージすることになります」
PL/SQLバッチ削除を使用することをお勧めします。数時間は大丈夫だと思います。