Bonjour à tous! Je suis @hiroki_tanaka, le producteur de Mayu Sakuma.
Je suis impliqué dans la maintenance des applications Rails, et l'autre jour, il s'est avéré qu'il y avait beaucoup de données indésirables dans l'environnement de production. C'est pourquoi j'ai envisagé comment supprimer une grande quantité de données dans Rails, j'ai donc résumé ce que j'avais étudié.
Premièrement, Rails a deux méthodes de suppression de données, détruire et supprimer. Je voudrais résumer brièvement les différences entre eux.
destroy/destroy!
Supprime un enregistrement spécifié via ActiveRecord.
Les méthodes de rappel (telles que before_destroy
et ʻafter_destroy) et la validation fonctionnent via ActiveRecord. De plus, s'il y a un modèle associé avec
dependant :: destroy` défini dans le modèle à supprimer, le modèle d'ensemble est également supprimé.
Si destroy rencontre une erreur lors de l'exécution et ne peut pas être supprimé, il renvoie uniquement false et ne renvoie pas d'exception.
En revanche, destroy! Renvoie une exception.
Par conséquent, si vous souhaitez intercepter explicitement l'erreur de suppression, il est préférable d'utiliser destroy!.
delete
Émettez SQL (instruction DELETE) directement dans la base de données sans passer par ActiveRecord pour supprimer l'enregistrement cible.
La méthode de rappel et la validation ne fonctionnent pas car elles ne passent pas par ActiveRecord.
De plus, même s'il y a un modèle associé à dependant :: destroy
dans le modèle à supprimer, il ne sera pas supprimé.
Le comportement au moment de l'échec est comme destroy, il ne renvoie que false et ne renvoie pas d'exception.
Depuis delete! N'existe pas dans delete, je pense qu'il vaut mieux utiliser destroy! Obéissant quand "je veux supprimer des données et renvoyer une erreur en cas d'échec".
destory_all
Un seul enregistrement peut être supprimé avec destroy / destroy!, Mais history_all peut spécifier plusieurs enregistrements et supprime tous les enregistrements spécifiés.
Comme destroy, history_all passe également par ActiveRecord, donc la méthode de rappel et la validation · dependant :: destroy
fonctionnent.
Comme destroy, destory_all provoque une erreur lors de l'exécution, et si le processus de suppression échoue au milieu, il ne renvoie que false et ne renvoie pas d'exception.
Cependant, il n'y a pas de méthode appelée destory_all!, Donc si vous voulez supprimer une grande quantité de données, mais si cela échoue, vous devez renvoyer une erreur correctement. (La méthode sera décrite plus tard.)
delete_all
Supprime les multiples enregistrements spécifiés sans passer par ActiveRecord.
Tout comme la suppression, la méthode de rappel et la validation · dependant :: destroy
ne fonctionnent pas.
Comme delete, delete_all provoque également une erreur lors de l'exécution, et si le processus de suppression échoue au milieu, il ne renvoie que false et ne renvoie pas d'exception.
Personnellement, je ne pense pas qu'il y ait beaucoup de situations où il est utilisé, mais le traitement sans ActiveRecord est plus rapide que destroy et story_all.
Par conséquent, je pense qu'il peut être utilisé dans des situations où vous souhaitez supprimer une grande quantité de données à la fois sans vous soucier des exceptions, des rappels et des éléments associés.
Je voudrais voir comment supprimer une grande quantité de données dans le sujet principal.
dependent :: destroy
Modèle: OuiParmi les modèles à supprimer cette fois, il y a aussi des modèles associés au callback et à dependante :: destroy
.
En tant qu'exigence, le modèle associé doit également être supprimé et je souhaite détecter explicitement s'il y a une erreur pendant le traitement.
Delete / delete_all / destroy_all n'est pas disponible à ce stade.
Ensuite, vous devez effectuer le processus de suppression dans les coulisses de l'exécution de l'application de production.
Donc, je ne veux pas prendre une méthode qui met une charge extrême sur la base de données.
animals = Animal.where(type: 'dog') #Extraction de données à supprimer
animals.each do |animal|
animal.destroy!
end
La façon la plus simple d'y penser est d'avoir un code comme celui-ci. Cependant, il y a deux problèmes.
―― La charge est lourde car destroy continue de fonctionner 100 000 fois.
Par conséquent, s'il y a de la capacité disponible dans la base de données, si l'application a une heure de fermeture claire et s'il n'y a pas de problème même si la suppression ne revient pas au milieu de l'échec, cette méthode convient si l'intégrité des données n'est pas un problème. Je pense.
animals = Animal.where(type: 'dog') #Extraction de données à supprimer
ActiveRecord::Base.transaction do
animals.each do |animal|
animal.destroy!
end
end
Le processus de suppression de la méthode (1) est défini sur une transaction. Si une erreur se produit au milieu du processus de suppression en effectuant une seule transaction, tous les processus de suppression seront annulés et la restauration sera effective. Par conséquent, si vous utilisez cette méthode, vous pouvez supprimer en toute sécurité toutes les données qui nécessitent l'intégrité des données.
Une mise en garde est que vous devez toujours utiliser destroy! Lorsque vous créez explicitement une transaction. Cela est dû au fait que destroy ne déclenche pas d'exception et renvoie false même si une erreur se produit, de sorte que le processus ne s'arrête pas et la transaction ne peut pas être quittée.
animals = Animal.where(type: 'dog') #Extraction de données à supprimer
ActiveRecord::Base.transaction do
animals.in_batches.each do |delete_target_animals|
delete_target_animals.map(&:destroy!)
sleep(0.1)
end
end
La méthode ci-dessus est la méthode adoptée cette fois.
Utilisez la méthode ActiveRecord :: Relation # in_batches pour combiner 100 000 enregistrements en 1 000 unités. Et détruisez! Pour chacun des morceaux.
Ensuite, lorsque le processus de suppression pour 1000 éléments est terminé, le processus est arrêté pendant 0,1 seconde avec sleep (0,1)
pour réduire la charge sur la base de données.
De plus, comme une transaction importante est placée à l'extérieur, même si une erreur se produit pendant le processus de suppression, tout sera annulé et la refonte sera efficace, donc c'est sûr.
animals = Animal.where(type: 'dog') #Extraction de données à supprimer
ActiveRecord::Base.transaction do
animals.in_batches(of: 10000).each do |delete_target_animals|
delete_target_animals.map(&:destroy!)
sleep(0.1)
end
end
Je pense qu'il existe différentes façons de supprimer de grandes quantités de données, en fonction de vos besoins. Par conséquent, si vous avez la meilleure pratique, j'aimerais avoir de vos nouvelles (o._.) O Peco