web-dev-qa-db-fra.com

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

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 crois 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?

19
Coding Gorilla

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;
18
René Nyffenegger

La solution classique est de partitionner vos tables, par ex. 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 un UNION implicite lors 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 à un TRUNCATE en termes de charge car c'est ce que vous faites vraiment - tronquer ou supprimer 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 celle-ci avec un DBMS_JOB).

Et avec les partitions, vous pouvez également exploiter requête parallèle et élimination de partition , ce qui devrait rendre vos utilisateurs très heureux ...

14
Gaius

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 pense 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 pouvez 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.

4
Gary

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.

2
Jason Jakob

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_old et renommez la table "work" en my_table. Une fois que vous êtes à l'aise avec tout drop my_table_old purge pour se débarrasser de l'ancienne table. S'il y a un tas de restrictions de clés étrangères, jetez un œil à dbms_redefinitionpackage 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 AskTom renommée. 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 pendant que le système est en marche, et le temps d'arrêt des applications 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
Mark Stewart

lors de la suppression d'une partition, vous laissez les index globaux inutilisables, qui doivent être reconstruits, la reconstruction des index globaux serait un gros problème, comme 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 par lots PL/SQL, plusieurs heures est ok je pense.

0
iceburge5