web-dev-qa-db-fra.com

Utiliser mysqldump pour formater une insertion par ligne?

Cela a été demandé à quelques reprises mais je ne trouve pas de solution à mon problème. En gros, lorsque j'utilise mysqldump, l'outil intégré de l'outil d'administration de MySQL Workbench, lorsque je dumpais une base de données à l'aide d'insertions étendues, j'obtenais de longues lignes de données volumineuses. Je comprends pourquoi, car cela accélère les insertions en insérant les données sous forme de commande unique (en particulier sur InnoDB), mais le formatage rend VRAIMENT difficile la consultation des données dans un fichier de vidage ou la comparaison de deux fichiers avec un outil de comparaison. si vous les stockez dans le contrôle de version, etc. Dans mon cas, je les stocke dans le contrôle de version car nous utilisons les fichiers de vidage pour garder une trace de notre base de données de test d'intégration.

Maintenant, je sais que je peux désactiver les insertions étendues. Je vais donc en obtenir une par ligne, ce qui fonctionne, mais chaque fois que vous effectuez une restauration avec le fichier de vidage, cela sera plus lent.

Mon principal problème est que, dans le vieil outil que nous utilisions auparavant (administrateur MySQL) lorsque je vidais un fichier, il faisait essentiellement la même chose, mais formaterait cette instruction INSERT pour mettre une insertion par ligne, tout en effectuant des insertions en bloc. Donc au lieu de cela:

INSERT INTO `coupon_gv_customer` (`customer_id`,`amount`) VALUES (887,'0.0000'),191607,'1.0300');

vous obtenez ceci:

INSERT INTO `coupon_gv_customer` (`customer_id`,`amount`) VALUES 
 (887,'0.0000'),
 (191607,'1.0300');

Quelles que soient les options que j'essaie, il ne semble pas y avoir de moyen d'obtenir un vidage comme celui-ci, qui est vraiment le meilleur des deux mondes. Oui, cela prend un peu plus de place, mais dans les situations où vous avez besoin d'un humain pour lire les fichiers, cela le rend BEAUCOUP plus utile.

Est-ce que quelque chose me manque et qu'il existe un moyen de le faire avec MySQLDump, ou avons-nous tous fait marche arrière et cette fonctionnalité de l'ancien outil administrateur de MySQL (désormais obsolète) n'est plus disponible?

40
Kendall Bennett

Avec le format mysqldump par défaut, chaque enregistrement vidé générera une commande INSERT individuelle dans le fichier de vidage (c'est-à-dire, le fichier SQL), chacun sur sa propre ligne. C'est parfait pour le contrôle de source (par exemple, svn, git, etc.), car il affine la résolution diff et delta beaucoup plus finement, et aboutit finalement à un processus de contrôle de source plus efficace. Cependant, pour toutes les tables de taille importante, l'exécution de toutes ces requêtes INSERT peut potentiellement ralentir la restauration à partir du fichier SQL.

L'utilisation de l'option --extended-insert résout le problème d'insert multiples en encapsulant tous les enregistrements dans une seule commande INSERT sur une seule ligne du fichier SQL vidé. Cependant, le processus de contrôle à la source devient très inefficace. Tout le contenu de la table est représenté sur une seule ligne du fichier SQL. Si un seul caractère change à un endroit quelconque de cette table, le contrôle de code source marquera la ligne entière (c'est-à-dire la table entière) en tant que delta entre les versions. Et, pour les grandes tables, cela annule de nombreux avantages liés à l'utilisation d'un système de contrôle de source formel.

Donc, idéalement, pour une restauration efficace de la base de données, dans le fichier SQL, nous voulons que chaque table soit représentée par un seul INSERT. Pour un processus de contrôle de source efficace, dans le fichier SQL, nous voulons que chaque enregistrement de cette commande INSERT réside sur sa propre ligne.

Ma solution à cela est le script de sauvegarde suivant:

#!/bin/bash

cd my_git_directory/

ARGS="--Host=myhostname --user=myusername --password=mypassword --opt --skip-dump-date"
/usr/bin/mysqldump $ARGS --database mydatabase | sed 's$VALUES ($VALUES\n($g' | sed 's$),($),\n($g' > mydatabase.sql

git fetch Origin master
git merge Origin/master
git add mydatabase.sql
git commit -m "Daily backup."
git Push Origin master

Le résultat est un format de commande INSERT de fichier SQL ressemblant à ceci:

INSERT INTO `mytable` VALUES
(r1c1value, r1c2value, r1c3value),
(r2c1value, r2c2value, r2c3value),
(r3c1value, r3c2value, r3c3value);

Quelques notes:

  • mot de passe sur la ligne de commande ... Je sais, pas sécurisé, discussion différente.
  • --opt: active entre autres l’option --extended-insert (c’est-à-dire un INSERT par table).
  • --skip-dump-date: mysqldump place normalement un horodatage dans le fichier SQL lors de sa création. Cela peut devenir gênant dans le contrôle de code source lorsque le seul delta entre les versions est cet horodatage. Le système d’exploitation et le système de contrôle des sources marquent la date/heure du fichier et de la version. Ce n'est pas vraiment nécessaire dans le fichier SQL.
  • Les commandes git ne sont pas au cœur de la question fondamentale (formater le fichier SQL), mais montrent comment je récupère mon fichier SQL dans le contrôle de source, une opération similaire peut être réalisée avec svn. En combinant ce format de fichier SQL avec le contrôle de source de votre choix, vous constaterez que lorsque vos utilisateurs mettent à jour leurs copies de travail, ils n'ont plus qu'à déplacer les deltas (enregistrements modifiés) sur Internet, et ils peuvent tirer parti des utilitaires diff. pour voir facilement quels enregistrements de la base de données ont changé.
  • Si vous videz une base de données qui réside sur un serveur distant, si possible, exécutez ce script sur ce serveur pour éviter de transférer tout le contenu de la base de données sur le réseau à chaque vidage.
  • Si possible, établissez un référentiel de contrôle de source de travail pour vos fichiers SQL sur le même serveur que celui sur lequel vous exécutez ce script; vérifiez-les dans le référentiel à partir de là. Cela vous évitera également de devoir transférer toute la base de données sur le réseau à chaque vidage. 
27
Todd Blumer

Essayez d'utiliser l'option suivante: --skip-extended-insert

Cela a fonctionné pour moi.

31
Eric Tan

Comme d'autres l'ont déjà dit, l'utilisation de sed pour remplacer "), (" n'est pas sans danger, car cela peut apparaître en tant que contenu dans la base de données . Cependant, il existe un moyen de le faire:. Suivant:

$ mysqldump -u my_db_user -p -h 127.0.0.1 --skip-extended-insert my_database > my_database.sql
$ sed ':a;N;$!ba;s/)\;\nINSERT INTO `[A-Za-z0-9$_]*` VALUES /),\n/g' my_database.sql > my_database2.sql

vous pouvez également utiliser "sed -i" pour remplacer in-line.

Voici ce que fait ce code:

  1. --skip-extended-insert créera un INSERT INTO pour chaque ligne que vous avez.
  2. Nous utilisons maintenant sed pour nettoyer les données. Notez que la recherche/remplacement régulier par sed s’applique à une seule ligne; nous ne pouvons donc pas détecter le caractère "\ n", car sed fonctionne une ligne à la fois. C'est pourquoi nous mettons ": a; N; $! Ba;" ce qui indique fondamentalement à sed de rechercher plusieurs lignes et de mettre en mémoire tampon la ligne suivante.

J'espère que cela t'aides

10
Ace.Di

Pourquoi ne pas stocker le dump dans un fichier CSV avec mysqldump, en utilisant l'option --tab comme ceci?

mysqldump --tab=/path/to/serverlocaldir --single-transaction <database> table_a

Cela produit deux fichiers:

  • table_a.sql qui contient uniquement l'instruction de création de table; et
  • table_a.txt qui contient des données séparées par des tabulations.

RESTAURATION

Vous pouvez restaurer votre table via LOAD DATA:

LOAD DATA INFILE '/path/to/serverlocaldir/table_a.txt' 
  INTO TABLE table_a FIELDS TERMINATED BY '\t' ...

LOAD DATA est généralement 20 fois plus rapide que l’utilisation d’instructions INSERT.

Si vous devez restaurer vos données dans une autre table (par exemple à des fins de révision ou de test), vous pouvez créer une table "miroir":

CREATE TABLE table_for_test LIKE table_a;

Puis chargez le fichier CSV dans la nouvelle table:

LOAD DATA INFILE '/path/to/serverlocaldir/table_a.txt' 
  INTO TABLE table_for_test FIELDS TERMINATED BY '\t' ...

COMPARER

Un fichier CSV est plus simple pour les diffs ou pour regarder à l'intérieur, ou pour les utilisateurs techniques non SQL qui peuvent utiliser des outils communs tels que Excel, Access ou la ligne de commande (diff, comm, etc ...)

7
Cristian Porta

J'ai bien peur que ce ne soit pas possible. Dans l'ancien administrateur MySQL, j'avais écrit le code de vidage des objets de base de données, qui était complètement indépendant de l'outil mysqldump et qui proposait donc un certain nombre d'options supplémentaires (comme ce formatage ou ce retour de progression). Dans MySQL Workbench, il a été décidé d’utiliser l’outil mysqldump qui, outre le fait de revenir en arrière à certains égards et de générer des problèmes de version, présente l’avantage de rester constamment à jour avec le serveur.

La réponse courte est donc: le formatage n'est actuellement pas possible avec mysqldump.

5
Mike Lischke

J'ai trouvé cet outil très utile pour traiter les insertions étendues: http://blog.lavoie.sl/2014/06/split-mysqldump-extended-inserts.html

Il analyse la sortie de mysqldump et insère des sauts de ligne après chaque enregistrement, tout en utilisant les insertions étendues les plus rapides. Contrairement à un script sed, il ne devrait pas y avoir de risque de coupure de lignes au mauvais endroit si la regex correspond à l'intérieur d'une chaîne.

1
seanf

J'ai aimé la solution de Ace.Di avec sed, jusqu'à ce que j'ai cette erreur: Sed: Impossible de réallouer de la mémoire

J'ai donc dû écrire un petit script PHP

mysqldump -u my_db_user -p -h 127.0.0.1 --skip-extended-insert my_database | php mysqlconcatinserts.php > db.sql

Le script PHP génère également une nouvelle instruction INSERT pour chaque 10 000 lignes afin d'éviter les problèmes de mémoire.

mysqlconcatinserts.php:

#!/usr/bin/php
<?php
/* assuming a mysqldump using --skip-extended-insert */
$last = '';
$count = 0;
$maxinserts = 10000;
while($l = fgets(STDIN)){
  if ( preg_match('/^(INSERT INTO .* VALUES) (.*);/',$l,$s) )
  {
    if ( $last != $s[1] || $count > $maxinserts )
    {
      if ( $count > $maxinserts ) // Limit the inserts
        echo ";\n";
      echo "$s[1] ";
      $comma = ''; 
      $last = $s[1];
      $count = 0;
    }
    echo "$comma$s[2]";
    $comma = ",\n";
  } elseif ( $last != '' ) {
    $last = '';
    echo ";\n";
  }
  $count++;
} 
0
Kjeld Flarup