Meilleure façon de supprimer un très grand jeu d'enregistrements dans Oracle


18

Je gère une application qui a une très grande base de données Oracle (près de 1 To de données avec plus de 500 millions de lignes dans une table). La base de données ne fait vraiment rien (pas de SProcs, pas de déclencheurs ou quoi que ce soit), c'est juste un magasin de données.

Chaque mois, nous devons purger les enregistrements des deux tableaux principaux. Les critères de purge varient et sont une combinaison de l'âge des lignes et de quelques champs d'état. Nous finissons généralement par purger entre 10 et 50 millions de lignes par mois (nous ajoutons environ 3 à 5 millions de lignes par semaine via les importations).

Actuellement, nous devons effectuer cette suppression par lots d'environ 50 000 lignes (par exemple, supprimer 50000, comit, supprimer 50000, valider, répéter). Si vous tentez de supprimer le lot entier en une seule fois, la base de données ne répond plus pendant environ une heure (en fonction du nombre de lignes). La suppression des lignes en lots comme celui-ci est très rude sur le système et nous devons généralement le faire "si le temps le permet" au cours d'une semaine; permettre au script de s'exécuter en continu peut entraîner une dégradation des performances inacceptable pour l'utilisateur.

Je pense que ce type de suppression par lots dégrade également les performances de l'index et a d'autres impacts qui finissent par dégrader les performances de la base de données. Il y a 34 index sur une seule table, et la taille des données d'index est en fait plus grande que les données elles-mêmes.

Voici le script utilisé par l'un de nos informaticiens pour effectuer cette purge:

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;

Cette base de données doit être en hausse de 99,99999% et nous n'avons qu'une fenêtre de maintenance de 2 jours une fois par an.

Je cherche une meilleure méthode pour supprimer ces enregistrements, mais je n'en ai pas encore trouvé. Aucune suggestion?


Notez également qu'il y a plus de 30 index en jeu ici
jcolebrand

Réponses:


17

La logique avec 'A' et 'B' peut être "cachée" derrière une colonne virtuelle sur laquelle vous pouvez faire le partitionnement:

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;

J'ai peut-être trop simplifié la logique derrière la façon dont les enregistrements à purger sont déterminés, mais c'est une idée très intéressante. Une chose qui doit cependant être prise en compte est la performance au jour le jour. La purge est "notre problème", le client n'acceptera pas les performances dégradées juste pour résoudre cela. Il semble, d'après certains commentaires et la réponse de Gary, que cela pourrait être un problème de partitionnement?
Coding Gorilla

Je ne sais pas si c'est la réponse que nous recherchons, mais c'est certainement une approche très intéressante que nous étudierons.
Coding Gorilla

14

La solution classique est de partitionner vos tables, par exemple par mois ou par semaine. Si vous ne les avez jamais rencontrés auparavant, une table partitionnée est comme plusieurs tables structurées de manière identique avec une implicite UNIONlors de la sélection, et Oracle stockera automatiquement une ligne dans la partition appropriée lors de son insertion en fonction des critères de partitionnement. Vous mentionnez les index - eh bien, chaque partition obtient également ses propres index partitionnés. C'est une opération très bon marché dans Oracle de supprimer une partition (c'est analogue à unTRUNCATEen termes de charge, car c'est ce que vous faites vraiment - tronquer ou supprimer l'une de ces sous-tables invisibles). Ce sera une quantité importante de traitement à répartir «après coup», mais cela n'a aucun sens de pleurer sur le lait renversé - les avantages de le faire l'emportent jusqu'à présent sur les coûts. Chaque mois, vous diviseriez la partition supérieure pour créer une nouvelle partition pour les données du mois suivant (vous pouvez facilement automatiser cela avec a DBMS_JOB).

Et avec les partitions, vous pouvez également exploiter l' élimination parallèle des requêtes et des partitions , ce qui devrait rendre vos utilisateurs très heureux ...


FWIW nous utilisons utiliser cette technique sur mon site sur une base de données 30 To +
Gaius

Le problème avec le partitionnement est qu'il n'y a pas de méthode claire pour partitionner les données. Dans l'un des deux tableaux (pas celui illustré ci-dessous), les critères utilisés pour effectuer la purge sont basés sur deux champs de date différents (et distincts) et un champ d'état. Par exemple, si le statut est Aalors DateAsupérieur à 3 ans, il est purgé. Si le statut est Bet date de DateBplus de 10 ans, il est purgé. Si ma compréhension du partitionnement est correcte, alors le partitionnement ne serait pas utile dans une situation comme celle-ci (au moins en ce qui concerne la purge).
Coding Gorilla

Vous pouvez partitionner par statut et sous-partitionner par plage de dates. Mais si le statut (ou la date) change, il supprime effectivement une sous-partition et une insertion dans l'autre. En bref, vous pouvez obtenir un coup sur vos processus quotidiens pour gagner du temps sur votre purge.
Gary

6
Vous pouvez également créer une colonne virtuelle qui affiche DateA lorsque l'état est A et DateB lorsque l'état est B, puis partitionner sur la colonne virtuelle. La même migration de partition se produirait, mais cela aiderait votre purge. Il semble que cela ait déjà été publié comme réponse.
Leigh Riffel

4

Un aspect à considérer est la proportion des performances de suppression des index et celle de la table brute. Chaque enregistrement supprimé de la table nécessite la même suppression de la ligne de chaque index btree. Si vous avez plus de 30 index btree, je soupçonne que la plupart de votre temps est consacré à la maintenance des index.

Cela a un impact sur l'utilité du partitionnement. Disons que vous avez un index sur le nom. Un index Btree standard, tout en un segment, peut avoir à effectuer quatre sauts pour passer du bloc racine au bloc feuille et une cinquième lecture pour obtenir la ligne. Si cet index est partitionné en 50 segments et que vous n'avez pas la clé de partition dans le cadre de la requête, chacun de ces 50 segments devra être vérifié. Chaque segment sera plus petit, vous n'aurez donc peut-être qu'à effectuer 2 sauts, mais vous pourrez toujours finir par faire 100 lectures au lieu des 5 précédentes.

S'il s'agit d'index bitmap, les équations sont différentes. Vous n'utilisez probablement pas d'index pour identifier des lignes individuelles, mais plutôt des ensembles d'entre elles. Ainsi, plutôt qu'une requête utilisant 5 E / S pour renvoyer un seul enregistrement, elle utilisait 10 000 E / S. En tant que tel, la surcharge supplémentaire dans les partitions supplémentaires pour l'index n'aura pas d'importance.


2

la suppression de 50 millions d'enregistrements par mois par lots de 50 000 n'est que 1 000 itérations. si vous supprimez 1 toutes les 30 minutes, cela devrait répondre à vos besoins. une tâche planifiée pour exécuter la requête que vous avez publiée mais supprimez la boucle afin qu'elle ne s'exécute qu'une seule fois ne devrait pas entraîner une dégradation sensible pour les utilisateurs. Nous faisons à peu près le même volume d'enregistrements dans notre usine de fabrication qui fonctionne à peu près 24h / 24 et 7j / 7 et qui répond à nos besoins. Nous l'étalons en fait un peu plus de 10 000 enregistrements toutes les 10 minutes, qui s'exécutent en environ 1 ou 2 secondes sur nos serveurs Oracle Unix.


Qu'en est-il de la génération et de l'annulation massives de la suppression? Cela étouffe aussi les entrées / sorties ... l'approche basée sur la «suppression» devrait certainement être un NON .. NON pour les grandes tables.
pahariayogi

1

Si l'espace disque n'est pas limité, vous pouvez créer une copie "de travail" de la table, par exemple my_table_new, en utilisant CTAS (Create Table As Select) avec des critères qui omettraient les enregistrements à supprimer. Vous pouvez faire l'instruction create en parallèle et avec l'indicateur d'ajout pour la rendre rapide, puis créer tous vos index. Ensuite, une fois terminé, (et testé), renommez la table existante en my_table_oldet renommez la table "work" en my_table. Une fois que vous êtes à l'aise avec tout drop my_table_old purgepour vous débarrasser de l'ancienne table. S'il y a un tas de restrictions de clés étrangères, jetez un œil au dbms_redefinition package PL / SQL . Il clonera vos index, contraintes, etc. lors de l'utilisation des options appropriées. Ceci est un résumé d'une suggestion de Tom Kyte de AskTomla célébrité. Après la première exécution, vous pouvez tout automatiser, et la table de création devrait aller beaucoup plus vite, et peut être effectuée lorsque le système est en marche, et le temps d'arrêt de l'application serait limité à moins d'une minute pour renommer les tables. L'utilisation de CTAS sera beaucoup plus rapide que plusieurs suppressions de lots. Cette approche peut être particulièrement utile si vous n'avez pas de licence de partitionnement.

Exemple de CTAS, en conservant les lignes avec les données des 365 derniers jours et 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
Cela peut être envisagé si (a) la purge est une tâche ponctuelle. (b) si vous avez moins de lignes à conserver et la plupart des données à supprimer ...
pahariayogi

0

lorsque vous supprimez une partition, vous laissez les index globaux inutilisables, qui doivent être reconstruits, la reconstruction des index globaux serait un gros problème, car si vous le faites en ligne, ce sera assez lent, sinon vous avez besoin de temps d'arrêt. dans les deux cas, ne peut pas répondre à l'exigence.

"Nous finissons généralement par purger entre 10 et 50 millions de lignes par mois"

je recommanderais d'utiliser la suppression de lot PL / SQL, plusieurs heures est ok je pense.


1
Si vous avez une clé primaire, la suppression d'une partition ne devrait pas rendre les index globaux inutilisables. Mais si l'OP a beaucoup d'index globaux, il y aura un coût élevé pour supprimer des partitions. Dans un cas idéal, lorsque quelqu'un partitionne une table, le partitionnement est basé sur la clé primaire et n'a pas besoin d'index globaux. Que chaque requête puisse tirer parti de l'élagage de partition.
Gandolf989

@ Gandolf989 la suppression d'une partition rendra toujours un index global inutilisable
miracle173
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.