Question

J'ai récemment trouvé et corrigé un bogue sur un site sur lequel je travaillais qui a entraîné des millions de lignes en double de données dans un tableau qui sera assez grand même sans eux (toujours en millions). Je peux facilement trouver ces lignes en double et peut exécuter une seule requête de suppression pour les tuer toutes. Le problème est qu'essayer de supprimer autant de lignes en un seul coup verrouille la table pendant longtemps, que je voudrais éviter si possible. Les seules façons de voir pour me débarrasser de ces lignes, sans éliminer le site (en verrouillant la table) est:

  1. Écrivez un script qui exécutera des milliers de requêtes de suppression plus petites dans une boucle. Cela contournera théoriquement le problème de la table verrouillée car d'autres requêtes pourront entrer dans la file d'attente et exécuter entre les suppressions. Mais cela augmentera encore un peu la charge de la base de données et prendra beaucoup de temps à fonctionner.
  2. Renommez la table et recréez le tableau existant (il sera maintenant vide). Ensuite, faites mon nettoyage sur la table renommée. Renommez la nouvelle table, nommez l'ancien dos et fusionnez les nouvelles lignes dans la table renommée. C'est bien que prend beaucoup plus d'étapes, mais devrait faire le travail avec une interruption minimale. La seule partie délicate ici est que le tableau en question est un tableau de reportage, donc une fois qu'il est renommé hors du chemin et que celui vide a mis à sa place, tous les rapports historiques disparaissent jusqu'à ce que je le remette en place. De plus, le processus de fusion pourrait être un peu pénible en raison du type de données stockées. Dans l'ensemble, c'est mon choix probable en ce moment.

Je me demandais simplement si quelqu'un d'autre avait déjà eu ce problème et, dans l'affirmative, comment vous l'avez traité sans éliminer le site et, espérons-le, avec un minimum ou une interruption des utilisateurs? Si je vais avec le numéro 2, ou une approche différente et similaire, je peux planifier les trucs pour courir tard dans la nuit et faire la fusion tôt le lendemain matin et faire savoir aux utilisateurs à l'avance, donc ce n'est pas énorme. Je cherche juste à voir si quelqu'un a des idées pour une manière meilleure ou plus facile de faire le nettoyage.

Était-ce utile?

La solution

DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

Lavez, rincez, répétez jusqu'à ce que zéro rangs affectés. Peut-être dans un script qui dort une seconde ou trois entre itérations.

Autres conseils

Je recommanderais également d'ajouter des contraintes à votre table pour vous assurer que cela ne vous arrive plus. Un million de lignes, à 1000 par coup, prendront 1000 répétitions d'un script à terminer. Si le script s'exécute une fois toutes les 3,6 secondes, vous serez terminé en une heure. Pas de soucis. Il est peu probable que vos clients le remarquent.

Ce qui suit supprime 1 000 000 de dossiers, un à la fois.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done

Vous pouvez les regrouper et supprimer table_name où dans (id1, id2, .. idn) je suis sûr aussi avec beaucoup de difficulté

J'avais un cas d'utilisation de suppression de lignes 1M + dans la table de 25 m + dans le MySQL. J'ai essayé différentes approches comme les suppressions de lots (décrites ci-dessus).
J'ai découvert que le moyen le plus rapide (copie des enregistrements requis à la nouvelle table):

  1. Créez une table temporaire qui contient des ID.

Créer la table id_temp_table (temp_id int);

  1. Insérer des ID qui doivent être supprimés:

Insérer dans id_temp_table (temp_id) SELECT .....

  1. Créer une nouvelle table Table_New

  2. Insérez tous les enregistrements de la table à la table_new sans lignes inutiles qui sont dans id_temp_table

Insérer dans Table_New ...

  1. Renommer les tables

L'ensemble du processus a pris ~ 1h. Dans mon cas d'utilisation, une simple suppression de lot sur 100 enregistrements a pris 10 minutes.

J'utiliserais mk-archiver de l'excellent Maatkit Package des services publics (un tas de scripts Perl pour MySQL Management) Maatkit est de Baron Schwartz, l'auteur du livre O'Reilly "High Performance Mysql".

L'objectif est un travail à faible impact et vers l'avant pour grignoter les anciens données hors du tableau sans avoir beaucoup d'impact sur les requêtes OLTP. Vous pouvez insérer les données dans une autre table, qui ne doit pas être sur le même serveur. Vous pouvez également l'écrire dans un fichier dans un format adapté à la charge de données. Ou vous ne pouvez pas faire ni l'un ni l'autre, auquel cas c'est juste une suppression incrémentielle.

Il est déjà conçu pour archiver vos lignes indésirables en petits lots et en bonus, il peut enregistrer les lignes supprimées dans un fichier au cas où vous bousillerez la requête qui sélectionne les lignes à supprimer.

Aucune installation requise, il suffit de saisir http://www.maatkit.org/get/mk-archiver et exécutez Perldoc dessus (ou lisez le site Web) pour la documentation.

J'ai fait face à un problème similaire. Nous avions une très grande table, environ 500 Go de taille sans partitionnement et un seul index sur la colonne primaire_key. Notre maître était une carrière d'une machine, 128 cœurs et 512 concerts de RAM et nous avions aussi plusieurs esclaves. Nous avons essayé quelques techniques pour lutter contre la suppression de lignes à grande échelle. Je les énumérerai tous ici du pire au meilleur

  1. Récupérer et supprimer une ligne à la fois. C'est le pire absolu que vous puissiez faire. Donc, nous n'avons même pas essayé cela.
  2. Récupérer les premières lignes 'x' à partir de la base de données à l'aide d'une requête limite sur la colonne primaire_key, puis en vérifiant les ID de ligne à supprimer dans l'application et en tirant une seule requête de suppression avec une liste d'ID primaire_key. Donc, 2 requêtes par lignes «x». Maintenant, cette approche était bien, mais le faisant en utilisant un travail par lots supprimé environ 5 millions de lignes en 10 minutes environ, en raison desquelles les esclaves de notre DB MySQL ont été retardés de 105 secondes. LAG de 105 secondes dans une activité de 10 minutes. Donc, nous avons dû nous arrêter.
  3. Dans cette technique, nous avons introduit un décalage de 50 ms entre notre lot ultérieur de notre lot et les suppressions de taille «x» chacune. Cela a résolu le problème du décalage, mais nous supprimons maintenant 1,2 à 1,3 million de lignes par 10 minutes, contre 5 millions dans la technique n ° 2.
  4. Partitionner la table de la base de données, puis supprimer toutes les partitions lorsqu'elle n'est pas nécessaire. C'est la meilleure solution que nous ayons, mais elle nécessite une table pré-partitionnée. Nous avons suivi l'étape 3 parce que nous avions une table très ancienne non partitionnée avec uniquement d'indexation sur la colonne primaire_key. La création d'une partition aurait pris trop de temps et nous étions en mode de crise. Voici quelques liens liés au partitionnement que j'ai trouvé utile Référence officielle MySQL, Partionnement quotidien Oracle DB.

Donc, OMI, si vous pouvez vous permettre d'avoir le luxe de créer une partition dans votre table, optez pour l'option n ° 4, sinon, vous êtes coincé avec l'option n ° 3.

Faites-le en lots de Letons Say 2000 Rows à la fois. Commettre entre les deux. Un million de lignes ne sont pas tellement et ce sera rapide, sauf si vous avez de nombreux indices sur la table.

Selon le Documentation MySQL, TRUNCATE TABLE est une alternative rapide à DELETE FROM. Essaye ça:

TRUNCATE TABLE table_name

J'ai essayé cela sur des lignes de 50 m et cela a été fait en deux minutes.

Remarque: les opérations tronquées ne sont pas en service des transactions; Une erreur se produit lors de la tentative de substitution au cours d'une transaction active ou d'un verrouillage de table actif

Pour nous, le DELETE WHERE %s ORDER BY %s LIMIT %d La réponse n'était pas une option, car les critères où était lent (une colonne non indexée) et frappait Master.

Sélectionnez parmi une lecture-replia une liste de clés principales que vous souhaitez supprimer. Exporter avec ce type de format:

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

Utilisez le script de bash suivant pour saisir cette entrée et la lancer dans la suppression des instructions nécessite un bash ≥ 4 à cause de mapfile intégré]:

sql-chunker.sh (se souvenir de chmod +x moi, et changez le shebang pour pointer de votre exécutable Bash 4):

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "$2" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "$1" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"

Invoquez comme ça:

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

Cela vous donnera un fichier avec une sortie formaté comme ainsi (j'ai utilisé une taille de lot de 2):

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

Exécutez ensuite les instructions comme ainsi:

mysql --login-path=master billing < batch_1000.sql

Pour ceux qui ne connaissent pas login-path, c'est juste un raccourci pour se connecter sans taper le mot de passe dans la ligne de commande.

Je pense que la lenteur est due à "l'index cluster" de MySQL où les enregistrements réels sont stockés dans l'indice de clé primaire - de l'ordre de l'indice de clé primaire. Cela signifie que l'accès à un enregistrement via la clé primaire est extrêmement rapide car il ne nécessite qu'un seul disque, car l'enregistrement sur le disque là où il a trouvé la clé principale correcte dans l'index.

Dans d'autres bases de données sans index en cluster, l'index lui-même ne tient pas l'enregistrement mais simplement un "décalage" ou "emplacement" indiquant où l'enregistrement est situé dans le fichier de table, puis une deuxième récupération doit être effectuée dans ce fichier pour récupérer les données réelles .

Vous pouvez imaginer lors de la suppression d'un enregistrement dans un index en cluster que tous les enregistrements au-dessus de cet enregistrement dans le tableau doivent être déplacés vers le bas pour éviter que peut avoir changé cela).

Connaître ce qui précède ce que nous avons constaté que les suppressions vraiment dépensées dans MySQL ont été d'effectuer les suppressions dans l'ordre inverse. Cela produit le moins de mouvement d'enregistrement car vous supprimez les enregistrements de la fin du premier sens que les suppressions ultérieures ont moins d'objets à déplacer.

Je n'ai rien scripté pour le faire, et le faire correctement nécessiterait absolument un script, mais une autre option consiste à créer une nouvelle table en double et à sélectionner toutes les lignes que vous souhaitez y garder. Utilisez un déclencheur pour le maintenir à jour pendant que ce processus se termine. Lorsqu'il est synchronisé (moins les lignes que vous souhaitez abandonner), renommez les deux tables dans une transaction, de sorte que la nouvelle prend la place de l'ancien. Déposez l'ancienne table et le tour est joué!

Cela (évidemment) nécessite beaucoup d'espace disque supplémentaire et peut taxer vos ressources d'E / S, mais sinon, peut être beaucoup plus rapide.

Selon la nature des données ou en cas d'urgence, vous pouvez renommer l'ancienne table et créer une nouvelle table vide à sa place, et sélectionner les lignes "Keep" dans la nouvelle table à votre guise ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top