Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
split_sqldump() { if [ $# -lt 1 ] ; then echo "Il faut passer le fichier de sqldump en argument (et éventuellement un préfixe en 2nd arg)"; elif [ ! -f "$1" ] ; then echo "Le fichier '$1' n'existe pas"; else prefix="$2"; file="$1"; awk -F '`' ' BEGIN {l=0;lastl=1; tb=""; prefix="'"$prefix"'"; file="'"$file"'"} {l=l+1} /DROP TABLE IF EXISTS/ { lasttb=tb; tb=$2; if (lasttb != "") { print "echo "[`date '"'+%T'"'`] " lasttb " (" lastl "," l-1 ")""; print "sed -ne '"'"'" lastl "," l-1 " p; " l " q;'"'"' < "'"$file"'" > " prefix lasttb ".sql"; } lastl=l; } END { print "echo "[`date '"'+%T'"'`] " tb " (" lastl ",fin)""; print "sed -ne '"'"'" lastl ",$ p;'"'"' < "'"$file"'" > " prefix tb ".sql"; } ' < $file; fi; };
Ça marche bien mais c'est assez lent. Même si c'est pas très grave ici (sur un desktop, et pas lancé très souvent) je me dis qu'il doit y avoir une façon de faire plus intelligente qui m'a échappée.
Avec un fichier test.sql de 16Mo et 100 tables, j'ai
$ time split_sqldump_ok test.sql prefix_ > todo real 0m2.182s (donc c'est pas awk qui patauge tant que ça)
Ça donne du $head todo echo "[`date '+%T'`] access (22,50)" sed -ne '22,50 p; 51 q;' < "test.sql" > prefix_access.sql echo "[`date '+%T'`] accesslog (51,704)" sed -ne '51,704 p; 705 q;' < "test.sql" > prefix_accesslog.sql echo "[`date '+%T'`] actions (705,742)" sed -ne '705,742 p; 743 q;' < "test.sql" > prefix_actions.sql echo "[`date '+%T'`] actions_aid (743,766)" sed -ne '743,766 p; 767 q;' < "test.sql" > prefix_actions_aid.sql echo "[`date '+%T'`] adodb_logsql (767,798)" sed -ne '767,798 p; 799 q;' < "test.sql" > prefix_adodb_logsql.sql
Jusque là tout va bien, mais à l'éxécution ça donne [02:53:20] access (22,50) [02:53:20] accesslog (51,704) [02:53:20] actions (705,742) [02:53:20] actions_aid (743,766) [02:53:20] adodb_logsql (767,798) ... [02:53:49] flood (2868,2896) [02:53:50] forum (2897,2924) [02:53:51] history (2925,2973) [02:53:52] languages (2974,3009) ... [02:55:00] vocabulary_node_types (32599,32640) [02:55:02] votingapi_cache (32641,32704) [02:55:04] votingapi_vote (32705,32764) [02:55:06] watchdog (32765,41897)
Soit presque 3min pour parser partiellement (de moins en moins certes) test.sql 100 fois.
J'ai essayé d'ajouter du sed -i '1,X d' test.sql après chaque "sed -e" mais c'est pire (pas très étonnant, ça réduit le fichier au fur et à mesure mais l'écriture est coûteuse).
Avec l'ajout de if (l>5000) { print "sed -i '"'"'1," l-1 " d;'"'"' "'"$file"'"";l=1} ça efface le début du fichier dès que le point de départ dépasse la 5000e ligne, et on tombe à 45s. Avec 1000 à la place de 5000, j'arrive à 30s mais je trouve ça élevé. Y'a qqchose d'évident qui m'a échappé (vu l'heure c'est possible) ?
Merci pour les suggestions.
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
split_sqldump() { if [ $# -lt 1 ] ; then echo "Il faut passer le fichier de sqldump en argument (et éventuellement un préfixe en 2nd arg)"; elif [ ! -f "$1" ] ; then echo "Le fichier '$1' n'existe pas"; else prefix="$2"; file="$1"; awk -F '`' ' BEGIN {l=0;lastl=1; tb=""; prefix="'"$prefix"'"; file="'"$file"'"} {l=l+1} /DROP TABLE IF EXISTS/ { lasttb=tb; tb=$2; if (lasttb != "") { print "echo "[`date '"'+%T'"'`] " lasttb " (" lastl "," l-1 ")""; print "sed -ne '"'"'" lastl "," l-1 " p; " l " q;'"'"' < "'"$file"'" > " prefix lasttb ".sql"; } lastl=l; } END { print "echo "[`date '"'+%T'"'`] " tb " (" lastl ",fin)""; print "sed -ne '"'"'" lastl ",$ p;'"'"' < "'"$file"'" > " prefix tb ".sql"; } ' < $file; fi; };
Salut,
Je répond un peu à l'arrache parce que je n'ai pas le temps de te faire une réponse complète mais voilà quelques idées :
1) première règle quand tu fais du parsage, tu t'arranges pour ne parser qu'une seule fois (toi tu parses <nombre de tables>+1 fois, c'est ultra dégueulasse, et évidemment les perfs s'en ressentent (sans parler de la scalabilité en O(n) lors que tu pourrais faire du O(1)).
Le corollaire est la 2ème règle, qui est encore plus importante :
2) deuxième règle du parsage, tu parses en temps réel dès que c'est possible, i.e. tu fais comme si tu parsais un stream, et tu fournis les réponses asap. Je m'explique :
- gcc prend un fichier, le parse, puis fait sa sauce. Il met tout le fichier en ram, puis commence à travailler, puis une fois qu'il a fini il sort ce qu'il veut. Un peu comme tu as fait, sauf que toi tu as fait un for (i=0; i < 100; i++) autour de l'appel :-)
- sort parse un stream, mais il ne commence à printer qu'une fois qu'il a tout le stream en ram. évidemment c'est mieux mais toujours pas top.
- cat, lui, il parse un stream et en plus il affiche au fûr et à mesure (modulo le cache éventuel sur stdout), c'est le must du parsage. En général, les scripts sed/awk fonctionnent comme ça (par ligne, donc).
Donc pour finir, la bonne solution : tu écris un bête script (en sed, awk, perl ou ce que tu veux) qui lit une ligne, réalise un traitement sur cette ligne (en particulier un traitement dépendant de l'état, et un traitement qui peut changer l'état), puis tu réitère jusqu'à la fin. Pas de stockage nulle part, et un seul parsage.
Pour ton cas, et en perl, ça donne (écrit dans le mail donc non testé, désolé :-)) :
----- #!/usr/bin/perl use strict; use warnings;
my $db = undef; # Current database name my $table = undef; # Current table name my $fh = undef; # Current filehandle while (<>) { if (/^CREATE DATABASE .* `([^`]+)`/) { $db = $1; } elsif (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1 close $fh if $fh; # pas nécessaire vu que le open # suivant ferme automatiquement # le filehandle. open $fh, '>', "$db.$table.sql"; }
if ($db and $table) { print $fh $_ if $fh; } } close $fh; -----
Je ne suis pas sûr que ça donne strictement le même résultat que ton script vu sa complexité, mais bon ça me semble être un début pour toi.
Désolé encore une fois si j'ai loupé quelque chose dans ton mail, et j'espère t'avoir aidé un peu. N'hésites pas à commenter.
++ Stéphane.
PS: Pour une réponse pure bash, c'est tout à fait faisable si tu la veux, mais ce sera infiniment plus lent.
Stephane Jourdois wrote:
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
<snip jusqu'à la fin>
Hello euuh.. $ mysqldump -T /tmp/mydbdumpbytable mydb ? =) (pas la tête...)
On Tue, Sep 02, 2008 at 07:05:22PM +0200, Philippe Jacquot wrote:
Stephane Jourdois wrote:
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
<snip jusqu'à la fin>
Hello euuh.. $ mysqldump -T /tmp/mydbdumpbytable mydb ? =) (pas la tête...)
mdr, mais chui deg :-)
++
Stephane Jourdois wrote:
On Tue, Sep 02, 2008 at 07:05:22PM +0200, Philippe Jacquot wrote:
Stephane Jourdois wrote:
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
<snip jusqu'à la fin>
Hello euuh.. $ mysqldump -T /tmp/mydbdumpbytable mydb ? =) (pas la tête...)
mdr, mais chui deg :-)
++
Sinon, y'avait peut-être aussi: awk ' /^DROP TABLE IF EXISTS .*;$/ { split($0,A,"`"); # beurk table=A[2]; print > table; # si si, on peut getline; while ($0 ~ /^.*[^;]$/) { print > table; getline; }; print > table;}'
my half-a-cent au boulot, philippe
Philippe Jacquot a écrit :
Hello euuh.. $ mysqldump -T /tmp/mydbdumpbytable mydb ? =) (pas la tête...)
mdr, mais chui deg :-)
Mais non, faut pas, c'est quand même très utile.
Je connaissais l'option -T, mais quand tu dois regarder un vieux dump archivé (besoin de faire des diff sur certaines tables entre une archive et la version actuelle), c'est lourd de devoir le réimporter dans une base temporaire pour le réexporter table par table, et c'est beaucoup plus long que les 0.3s de ton script ;-)
Le 02/09/08 à 17:38, Stephane Jourdois kwisatz-shell@rubis.org a écrit :
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
Je répond un peu à l'arrache parce que je n'ai pas le temps de te faire une réponse complète mais voilà quelques idées :
Pour une réponse à l'arache, je la trouve déjà très complète ;-)
- première règle quand tu fais du parsage, tu t'arranges pour ne parser
qu'une seule fois (toi tu parses <nombre de tables>+1 fois, c'est ultra dégueulasse, et évidemment les perfs s'en ressentent (sans parler de la scalabilité en O(n) lors que tu pourrais faire du O(1)).
Oui, évidemment, c'est relativement crade, mais mon but était de découper rapidement mon dump en fichiers plus petits. Je ne m'attendais pas à un truc super rapide mais j'étais quand même étonné que cela n'aille pas plus vite.
Le 1er parsing de awk (pour trouver les n° de lignes où couper) est "relativement" rapide, en tout cas bien suffisant pour cette utilisation (2-3s), mais ensuite je pensais que sed irait plus vite lors de chaque
sed -ne '22,50 p; 51 q;' < "test.sql" > prefix_access.sql sed -ne '51,704 p; 705 q;' < "test.sql" > prefix_accesslog.sql
Même avec les n° de ligne, il parse le début du fichier, mais ensuite il s'arrête à la dernière ligne voulue (et ne parse donc pas la fin). Donc normalement, sed parse le début du fichier 100x.
Le corollaire est la 2ème règle, qui est encore plus importante :
- deuxième règle du parsage, tu parses en temps réel dès que c'est
possible, i.e. tu fais comme si tu parsais un stream, et tu fournis les réponses asap.
Sur le principe je comprend, mais je n'ai pas eu d'idée lumineuse pour l'appliquer à ce cas (je ne modifie pas les lignes mais les écrit dans un fichier différent suivant leur position). sed peut écrire dans un fichier (avec w) mais je ne vois pas comment rendre le nom du fichier variable (même en utilisant le holdspace), et il ne me semble pas que awk puisse écrire dans un fichier variable.
Je m'explique :
- gcc prend un fichier, le parse, puis fait sa sauce. Il met tout le fichier en ram, puis commence à travailler, puis une fois qu'il a fini
il sort ce qu'il veut. Un peu comme tu as fait, sauf que toi tu as fait un for (i=0; i < 100; i++) autour de l'appel :-)
sort parse un stream, mais il ne commence à printer qu'une fois qu'il a tout le stream en ram. évidemment c'est mieux mais toujours pas top.
cat, lui, il parse un stream et en plus il affiche au fûr et à mesure (modulo le cache éventuel sur stdout), c'est le must du parsage. En
général, les scripts sed/awk fonctionnent comme ça (par ligne, donc).
Donc pour finir, la bonne solution : tu écris un bête script (en sed, awk, perl ou ce que tu veux) qui lit une ligne, réalise un traitement sur cette ligne (en particulier un traitement dépendant de l'état, et un traitement qui peut changer l'état), puis tu réitère jusqu'à la fin. Pas de stockage nulle part, et un seul parsage.
Ça m'a donné une idée avec un seul passage sed qui insère des commandes cat dans le dump, qu'on exécute ensuite (sur 1 ligne) :
sed -e '/^--/ d; /^/*!/d; /^$/d; s/`//g; s/^DROP TABLE IF EXISTS ([^;]*)/marqueur-de-fin-de-split\ncat > \1.sql <<marqueur-de-fin-de-split\nDROP TABLE IF EXISTS \1/;' < dump.sql |sh
(il y a juste un marqueur en trop en 1re ligne et un qui manque en dernière ligne, mais ça marche)
Ça va un peu plus vite que mon truc bourrin de l'autre jour, mais pas tant que ça : sur un dump de 100 tables et 22Mo, sed + sh prend 21s (6s + 15s) et la solution awk qui génère des commandes sed + éxécution par le shell 26s.
Pour ton cas, et en perl, ça donne (écrit dans le mail donc non testé, désolé :-)) :
C'est déjà sympatique de donner du code commenté ! Vu ma connaissance symbolique de perl, j'avais éliminé d'office cette solution ;-)
#!/usr/bin/perl use strict; use warnings;
my $db = undef; # Current database name my $table = undef; # Current table name my $fh = undef; # Current filehandle while (<>) { if (/^CREATE DATABASE .* `([^`]+)`/) { $db = $1; } elsif (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1 close $fh if $fh; # pas nécessaire vu que le open # suivant ferme automatiquement # le filehandle. open $fh, '>', "$db.$table.sql"; }
if ($db and $table) { print $fh $_ if $fh; } } close $fh;
Je ne suis pas sûr que ça donne strictement le même résultat que ton script vu sa complexité, mais bon ça me semble être un début pour toi.
Ton script met 0.351s ;-)
Bon ben, il me reste à apprendre perl si je veux jouer dans la même cour...
Daniel
PS : Le script modifié utilisé (j'ai pas de CREATE DATABASE dans mes dump donc j'ai viré les ref à $db, ajouté un ; manquant et viré la ligne close qui lui plaisait pas) ----- #!/usr/bin/perl use strict; use warnings;
my $table = undef; # Current table name my $fh = undef; # Current filehandle
while (<>) { if (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1; open $fh, '>', "perl_$table.sql"; }
if ($table) { print $fh $_ if $fh; } } close $fh; -----
On Tue, Sep 02, 2008 at 09:43:44PM +0200, Daniel Caillibaud wrote:
Le 02/09/08 à 17:38, Stephane Jourdois kwisatz-shell@rubis.org a écrit :
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
PS : Le script modifié utilisé (j'ai pas de CREATE DATABASE dans mes dump donc j'ai viré les ref à $db, ajouté un ; manquant et viré la ligne close qui lui plaisait pas)
#!/usr/bin/perl use strict; use warnings;
my $table = undef; # Current table name my $fh = undef; # Current filehandle
while (<>) { if (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1; open $fh, '>', "perl_$table.sql"; }
if ($table) { print $fh $_ if $fh; } } close $fh;
Bon mis à part que tout ça ne sert à rien avec la bonne option de mysqldump, j'ai oublié de préciser comment invoquer ce script, dont tu peux profiter pour virer le dernier close qui ne sert à rien non plus :
mysqldump db | perl lescript.pl mysqldump db >file; perl lescript.pl file
ou plus élégant étant donné qu'on est tout de même sur shell@asyd.net :
perl lescript.pl <(mysqldump db)
(et tout ça pour rien ;-)
++
PS: (je suppose que le close ne lui plaisait pas parce qu'il n'y avait pas de parenthèses, ptet que "close($fh) if $fh;" lui plaît plus.