Beste Möglichkeit, um ein sehr großes Recordset in Oracle zu löschen

18

Ich verwalte eine Anwendung mit einem sehr großen Oracle-Datenbank-Backend (fast 1 TB Daten mit mehr als 500 Millionen Zeilen in einer Tabelle). Die Datenbank macht eigentlich gar nichts (keine SProcs, keine Trigger oder so), sie ist nur ein Datenspeicher.

Jeden Monat müssen wir Datensätze aus den beiden Haupttabellen entfernen. Die Kriterien für die Bereinigung variieren und sind eine Kombination aus Zeilenalter und einigen Statusfeldern. In der Regel werden zwischen 10 und 50 Millionen Zeilen pro Monat gelöscht (wir fügen wöchentlich etwa 3-5 Millionen Zeilen über Importe hinzu).

Derzeit müssen wir dies in Stapeln von ungefähr 50.000 Zeilen löschen (dh 50000 löschen, festschreiben, 50000 löschen, festschreiben, wiederholen). Wenn Sie versuchen, den gesamten Stapel auf einmal zu löschen, reagiert die Datenbank etwa eine Stunde lang nicht (abhängig von der Anzahl der Zeilen). Das Löschen der Zeilen in solchen Stapeln ist auf dem System sehr rau und muss in der Regel "zeitlich" über einen Zeitraum von einer Woche erfolgen. Das kontinuierliche Ausführen des Skripts kann zu einer für den Benutzer inakzeptablen Leistungsverschlechterung führen.

Ich glaube, dass diese Art des Stapellöschens auch die Indexleistung beeinträchtigt und andere Auswirkungen hat, die letztendlich zu einer Verschlechterung der Datenbankleistung führen. Es gibt 34 Indizes für nur eine Tabelle, und die Indexdatengröße ist tatsächlich größer als die Daten selbst.

Hier ist das Skript, mit dem einer unserer IT-Mitarbeiter diese Bereinigung durchführt:

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;

Diese Datenbank muss zu 99,99999% erweitert sein und wir haben nur ein 2-tägiges Wartungsfenster pro Jahr.

Ich suche nach einer besseren Methode zum Entfernen dieser Datensätze, aber ich habe noch keine gefunden. Irgendwelche Vorschläge?

Codierung Gorilla
quelle
Beachten Sie auch, dass hier mehr als 30 Indizes im Spiel sind
jcolebrand

Antworten:

17

Die Logik mit 'A' und 'B' könnte sich hinter einer virtuellen Spalte "verstecken", in der Sie die Partitionierung vornehmen können:

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;
René Nyffenegger
quelle
Möglicherweise habe ich die Logik für die Bestimmung der zu löschenden Datensätze stark vereinfacht, aber dies ist eine sehr interessante Idee. Eine Sache, die jedoch berücksichtigt werden muss, ist die tägliche Leistung. Das Löschen ist "unser Problem". Der Kunde akzeptiert keine Leistungseinbußen, um das Problem zu lösen. Aus einigen Kommentaren und Garys Antwort geht hervor, dass dies ein Problem mit der Partitionierung sein könnte.
Codierung Gorilla
Ich bin nicht sicher, ob dies die Antwort ist, nach der wir suchen, aber dies ist definitiv ein sehr interessanter Ansatz, den wir untersuchen werden.
Codierung Gorilla
14

Die klassische Lösung hierfür ist die Partitionierung Ihrer Tabellen, z. B. nach Monat oder nach Woche. Wenn Sie sie noch nicht kennen, ähnelt eine partitionierte Tabelle mehreren identisch strukturierten Tabellen, die UNIONbei der Auswahl implizit ausgewählt werden, und Oracle speichert automatisch eine Zeile in der entsprechenden Partition, wenn Sie sie basierend auf den Partitionskriterien einfügen. Sie erwähnen Indizes - nun, jede Partition erhält auch ihre eigenen partitionierten Indizes. Es ist eine sehr billige Operation in Oracle, eine Partition zu löschen (es ist analog zu aTRUNCATEin Bezug auf die Auslastung, weil Sie genau das tun - eine dieser unsichtbaren Untertabellen abschneiden oder löschen). Es wird eine beträchtliche Menge an Verarbeitung sein, um "nachträglich" zu partitionieren, aber es macht keinen Sinn, über verschüttete Milch zu weinen - die Vorteile, die dies bisher mit sich bringt, überwiegen die Kosten. Jeden Monat teilen Sie die oberste Partition, um eine neue Partition für die Daten des nächsten Monats zu erstellen (Sie können dies einfach mit einem automatisieren DBMS_JOB).

Und mit Partitionen können Sie auch die Beseitigung paralleler Abfragen und Partitionen ausnutzen , was Ihre Benutzer sehr glücklich machen sollte ...

Gaius
quelle
FWIW verwenden wir diese Technik auf meiner Website in einer Datenbank mit mehr als 30 TB
Gaius
Das Problem bei der Partitionierung ist, dass es keinen eindeutigen Weg gibt, die Daten zu partitionieren. In einer der beiden Tabellen (nicht der unten gezeigten) basieren die Kriterien für die Bereinigung auf zwei unterschiedlichen (und unterschiedlichen) Datumsfeldern und einem Statusfeld. Wenn der Status beispielsweise " Awenn DateAälter als 3 Jahre" ist, wird er gelöscht. Wenn der Status ist Bund DateBist älter als 10 Jahre, wird es gelöscht. Wenn ich die Partitionierung richtig verstehe, wäre die Partitionierung in einer solchen Situation nicht sinnvoll (zumindest was die Bereinigung betrifft).
Codierung Gorilla
Sie können nach Status und nach Datumsbereich unterteilen. Wenn sich der Status (oder das Datum) ändert, wird effektiv von einer Unterpartition gelöscht und in die andere eingefügt. Kurz gesagt, Sie können Ihre alltäglichen Abläufe optimieren, um Zeit beim Spülen zu sparen.
Gary
6
Alternativ können Sie eine virtuelle Spalte erstellen, die DateA anzeigt, wenn der Status A ist, und DateB, wenn der Status B ist, und dann in der virtuellen Spalte partitionieren. Dieselbe Partitionsmigration würde stattfinden, aber dies würde Ihre Bereinigung erleichtern. Es sieht so aus, als ob dies bereits als Antwort gepostet wurde.
Leigh Riffel
4

Ein zu berücksichtigender Aspekt ist, wie viel von der Löschleistung von Indizes und wie viel von der Basistabelle herrührt. Jeder aus der Tabelle gelöschte Datensatz erfordert dasselbe Löschen der Zeile aus jedem Btree-Index. Wenn Sie mehr als 30 Btree-Indizes haben, wird vermutlich der größte Teil Ihrer Zeit für die Indexpflege aufgewendet.

Dies hat Auswirkungen auf die Nützlichkeit der Partitionierung. Angenommen, Sie haben einen Index zum Namen. Ein Standard-Btree-Index, der sich alle in einem Segment befindet, muss möglicherweise vier Sprünge ausführen, um vom Stammblock zum Blattblock zu gelangen, und einen fünften Lesevorgang, um die Zeile zu erhalten. Wenn dieser Index in 50 Segmente unterteilt ist und Sie den Partitionsschlüssel nicht als Teil der Abfrage haben, muss jedes dieser 50 Segmente überprüft werden. Jedes Segment ist kleiner, so dass Sie möglicherweise nur zwei Sprünge ausführen müssen, am Ende jedoch möglicherweise 100 statt der vorherigen fünf Lesevorgänge.

Wenn es sich um Bitmap-Indizes handelt, unterscheiden sich die Gleichungen. Sie verwenden wahrscheinlich keine Indizes, um einzelne Zeilen zu identifizieren, sondern Sätze von ihnen. Anstelle einer Abfrage mit 5 E / A-Vorgängen zum Zurückgeben eines einzelnen Datensatzes wurden 10.000 E / A-Vorgänge verwendet. Daher spielt der zusätzliche Aufwand für zusätzliche Partitionen für den Index keine Rolle.

Gary
quelle
2

Das Löschen von 50 Millionen Datensätzen pro Monat in Chargen von 50.000 entspricht nur 1000 Iterationen. Wenn Sie alle 30 Minuten 1 löschen, sollte dies Ihrer Anforderung entsprechen. Eine geplante Aufgabe zum Ausführen der von Ihnen bereitgestellten Abfrage, zum Entfernen der Schleife, damit sie nur einmal ausgeführt wird, sollte keine spürbare Beeinträchtigung der Benutzer verursachen. Wir machen ungefähr das gleiche Volumen an Aufzeichnungen in unserer Produktionsstätte, das fast rund um die Uhr läuft und unseren Anforderungen entspricht. Tatsächlich verteilen wir alle 10 Minuten etwas mehr als 10.000 Datensätze, die in etwa 1 oder 2 Sekunden auf unseren Oracle-Unix-Servern ausgeführt werden.

Jason Jakob
quelle
Was ist mit massivem 'Rückgängig' und 'Wiederholen', das 'Löschen' erzeugt? Es drosselt auch I / O ... "Lösch" -basierter Ansatz sollte auf jeden Fall ein NEIN sein. NEIN für große Tabellen.
Pahariayogi
1

Wenn der Speicherplatz nicht knapp ist, können Sie möglicherweise eine Arbeitskopie der Tabelle my_table_newerstellen , z. B. mithilfe von CTAS (Create Table As Select) mit Kriterien, bei denen die zu löschenden Datensätze nicht berücksichtigt werden. Sie können die create-Anweisung parallel und mit dem append-Hinweis ausführen, um sie zu beschleunigen, und dann alle Ihre Indizes erstellen. Benennen Sie anschließend die vorhandene Tabelle in um my_table_oldund benennen Sie die Tabelle "work" in um , sobald sie fertig ist (und getestet wurde) my_table. Sobald Sie mit allem drop my_table_old purgevertraut sind, können Sie den alten Tisch loswerden. Wenn es eine Reihe von Fremdschlüsseleinschränkungen gibt, schauen Sie sich das dbms_redefinition PL / SQL-Paket an . Es wird Ihre Indizes, Beschränkungen usw. klonen, wenn Sie die entsprechenden Optionen verwenden. Dies ist eine Zusammenfassung eines Vorschlags von Tom Kyte von AskTomRuhm. Nach der ersten Ausführung können Sie alles automatisieren, und die Erstellung der Tabelle sollte viel schneller vonstatten gehen. Dies kann bei laufendem System erfolgen, und die Ausfallzeit der Anwendung ist auf weniger als eine Minute für das Umbenennen der Tabellen begrenzt. Die Verwendung von CTAS ist viel schneller als mehrere Batch-Löschvorgänge. Dieser Ansatz kann besonders nützlich sein, wenn Sie keine lizenzierte Partitionierung haben.

Beispiel-CTAS, in dem Zeilen mit Daten der letzten 365 Tage gespeichert werden und 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;
Mark Stewart
quelle
1
Dies kann in Betracht gezogen werden, wenn (a) das Spülen eine einmalige Aufgabe ist. (b) wenn Sie weniger Zeilen behalten und die meisten Daten zu entfernen ...
pahariayogi
0

Wenn Sie eine Partition löschen, lassen Sie globale Indizes unbrauchbar, die neu erstellt werden müssen. Die Neuerstellung globaler Indizes wäre ein großes Problem. Wenn Sie dies online tun, ist dies recht langsam. Andernfalls benötigen Sie Ausfallzeiten. In beiden Fällen kann nicht für die Anforderung passen.

"Normalerweise werden zwischen 10 und 50 Millionen Zeilen pro Monat gelöscht."

Ich würde empfehlen, mit PL / SQL Batch löschen, mehrere Stunden ist ok, denke ich.

iceburge5
quelle
1
Wenn Sie einen Primärschlüssel haben, sollte das Löschen einer Partition keine globalen Indizes unbrauchbar machen. Wenn das OP jedoch über viele globale Indizes verfügt, entstehen hohe Kosten für das Löschen von Partitionen. Im Idealfall basiert die Partitionierung beim Partitionieren einer Tabelle auf dem Primärschlüssel und benötigt keine globalen Indizes. Dass jede Abfrage das Bereinigen von Partitionen nutzen kann.
Gandolf989
@ Gandolf989 Das Löschen einer Partition macht einen globalen Index immer unbrauchbar
miracle173