Hallo,
ich habe regelmäßig (täglich) ein paar Daten aus einer proprietären Software anfallenden Textdateien in eine MySQL Datenbank zu packen. Als Admin kann ich mir mit BASH und MySQL ein wenig helfen, Programmierer bin ich damit nicht. Nun fällt erschreckend auf, wie langsam das Ganze ist und ich hoffe man kann mir hier helfen (im Zweifel dann auch bezahlt). Aber erstmal zu den Fakten:
So sieht die Datei aus (es sind dann Dutzende dieser Dateien, mit je Tausenden Zeilen):
03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:01:44 0,04413 03.05.2012 00:01:45 0,42134 03.05.2012 00:01:46 0,39369 03.05.2012 00:01:47 0,44423 03.05.2012 00:01:48 0,54073 03.05.2012 00:01:49 0,56819 03.05.2012 00:01:50 0,61855 ...
Sind mehrere Zeitstempel identisch, soll der _LETZTE_ Wert genommen werden.
Das ist meine Tabelle:
CREATE TABLE IF NOT EXISTS `test` ( `timestamp` datetime NOT NULL, `value` decimal(10,5) NOT NULL, PRIMARY KEY (`timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Ich vermute schon hier unüberlegt strukturiert zu haben. Ich möchte den Zeitstempel aber weiterverarbeitbar haben, da macht doch ein MySQL Format Sinn, auch wenn völliger Käse geliefert wird. Und weil ich ja nur den Letzten Eintrag bei identischen Zeitstempeln merken will, habe ich mich für "UPSERT" entschieden, also das Feld auf PRIMARY KEY gesetzt.
Und hier nun mein primitives Skript, welches auf einer Core i3 3GHz Maschine mit 8gb RAM so 20 bis 30 Zeilen in der Sekunde ins MySQL schickt:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Mit freundlichen Grüßen / Kind regards Ronny Seffner
Ronny Seffner ronny@seffner.de hat am 1. August 2012 um 18:43 geschrieben:
Hallo,
ich habe regelmäßig (täglich) ein paar Daten aus einer proprietären Software anfallenden Textdateien in eine MySQL Datenbank zu packen. Als Admin kann ich mir mit BASH und MySQL ein wenig helfen, Programmierer bin ich damit nicht. Nun fällt erschreckend auf, wie langsam das Ganze ist und ich hoffe man kann mir hier helfen (im Zweifel dann auch bezahlt). Aber erstmal zu den Fakten:
So sieht die Datei aus (es sind dann Dutzende dieser Dateien, mit je Tausenden Zeilen):
03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:01:44 0,04413 03.05.2012 00:01:45 0,42134 03.05.2012 00:01:46 0,39369 03.05.2012 00:01:47 0,44423 03.05.2012 00:01:48 0,54073 03.05.2012 00:01:49 0,56819 03.05.2012 00:01:50 0,61855 ...
Sind mehrere Zeitstempel identisch, soll der _LETZTE_ Wert genommen werden.
Das ist meine Tabelle:
CREATE TABLE IF NOT EXISTS `test` ( `timestamp` datetime NOT NULL, `value` decimal(10,5) NOT NULL, PRIMARY KEY (`timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Ich vermute schon hier unüberlegt strukturiert zu haben. Ich möchte den Zeitstempel aber weiterverarbeitbar haben, da macht doch ein MySQL Format Sinn, auch wenn völliger Käse geliefert wird. Und weil ich ja nur den Letzten Eintrag bei identischen Zeitstempeln merken will, habe ich mich für "UPSERT" entschieden, also das Feld auf PRIMARY KEY gesetzt.
Und hier nun mein primitives Skript, welches auf einer Core i3 3GHz Maschine mit 8gb RAM so 20 bis 30 Zeilen in der Sekunde ins MySQL schickt:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Viele Inserts (vor allem, wenn ein TX-basiertes System werkelt, und diese je in einer einzelnen TX laufen) kosten halt Zeit.
Mein Ansatz wäre aber generell anders: erst mal in die DB, als ein Textfeld. Demo mit PostgreSQL:
test=*# select * from ronny ; t --------------------------------- 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:01:44 0,04413 03.05.2012 00:01:45 0,42134 03.05.2012 00:01:46 0,39369 03.05.2012 00:01:47 0,44423 03.05.2012 00:01:48 0,54073 03.05.2012 00:01:49 0,56819 03.05.2012 00:01:50 0,61855 (18 rows)
Das einzulesen geht mit entsprechenden COPY-Befehl sehr flott.
Daraus kannst Du dann abfragen:
test=*# select distinct on (time) time, value from (select to_timestamp(substring(t,1,20),'dd.mm.yyyy hh24:mi:ss') as time, to_number(substring(t,22,50),'99D99999') as value from ronny) foo ; time | value ------------------------+--------- 2012-05-03 00:00:25+02 | 0.03296 2012-05-03 00:00:26+02 | 0.03296 2012-05-03 00:01:44+02 | 0.04413 2012-05-03 00:01:45+02 | 0.42134 2012-05-03 00:01:46+02 | 0.39369 2012-05-03 00:01:47+02 | 0.44423 2012-05-03 00:01:48+02 | 0.54073 2012-05-03 00:01:49+02 | 0.56819 2012-05-03 00:01:50+02 | 0.61855 (9 rows)
Noch eine Bemerkung zu Deinem _LETZTEN_ Datensatz: ohne ein ORDER BY gibt es in Mengen, mit denen Datenbanken hantieren, kein Oben und kein Unten, kein Vorne und kein Hinten. Das mußt Du genauer definieren.
Andreas
Noch eine Bemerkung zu Deinem _LETZTEN_ Datensatz: ohne ein ORDER BY gibt es in Mengen, mit denen Datenbanken hantieren, kein Oben und kein Unten, kein Vorne und kein Hinten. Das mußt Du genauer definieren.
Nachtrag:
das kommt ja aus Textdateien, die geben die Reihenfolge vor. En DB-Ansatz wäre, beim COPY eine SERIAL-Spalte (MySQL: AUTOINCREMENT) zu füllen und dann bei gelichen Zeitwerten den höchsten Wert aus der Serial zu nehmen. PG böte da z.B. ein Konstrukt a la select distinct on (timestamp) ... order by timestamp, serial desc ... an. Geht aber auch irknwie mit MySQL.
Andreas
Am 01.08.2012 18:43, schrieb Ronny Seffner:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Vielleicht… Aber vielleicht solltest Du auch nicht jedesmal mysql neu starten. Wenn Du schon sed verwendest, kannst Du auch gleich alles mit sed machen:
(cat prepare-insert.sql
sed -e \ "s"\ "%"\ "([0-9]+)[.:]([0-9]+)[.:]([0-9]+)[ \t]+([0-9:.]+)[ \t]+([0-9]*)[,.]([0-9]*)"\ "%"\ "INSERT INTO `test` (`timestamp`, `value`) "\ "VALUES ('\3-\2-\1 \4','\5.\6')"\ " ON DUPLICATE KEY "\ "UPDATE `timestamp`= '\3-\2-\1 \4', `value` ='\5.\6';"\ "%" \ "$FILE" ;
cat after-insert.sql ) | mysql machines -uroot -p$SQLPWD
Du kannst auch awk mit printf benutzen da kannst Du Dir auch jede Zeile mit einem ähnlichen oder demselben (ich weiß immer nicht wann wo Backlashes gesetzt werden müssen) regulären Ausdruck auseinandernehmen lassen. Du kannst auch das „Skript“ in eine Extradatei speichern.
Die beiden Dateien kannst Du dann noch benutzen um Zeugs drumrum zu basteln. Z.B. die Indizes während der Inserts ausschalten. Beim Einschalten werden sie dann aktualisiert. Das macht mysqldump auch so.
Tobias
Hallo Tobias,
der Gedanke gefällt mir, das Textfile in MySQL Anweisungen umzustrukturieren. Nur komme ich mit Deinem Beispiel allein in der Verwendung nicht klar.
Das Ganze in ein file kopiert und cat textfile | sed -e sed.script liefert eine Fehlermeldung, dass der "s"-Befehl nicht beendet sei. Beispiele von sed-Scritps, die google so liefert kommen ohne die vielen " aus und wirken ein wenig anders.
Wie kann ich Dein Beispiel verwenden?
Mit freundlichen Grüßen / Kind regards Ronny Seffner
Hallo,
auch wenn das Problem schon gelöst ist: Die Anführungsstriche sind dafür da, dass ich mir keinen Kopf um die shell machen muss.
Wenn Du Dir den relevanten Teil mal in einer shell eingibst, und Dir den ausgeführten Befehl wiederholst, wirst Du feststellen, dass sich da ein Zeilenumbruch eingeschlichen hat.
Es gibt zwei Möglichkeiten: Zeile 4 und 5 mit Abstand eines Leerzeichens aneinanderzuhängen oder Zeile 4 nach einem Leerzeichen mit „"\“ abzuschließen (ohne „“) und die nächste mit den Schreibmaschinenanführungszeihen zu beginnen.
Bei näherem Hinsehen wird wohl mein Vorschlag, die Indices zu deaktivieren evtl. mehr Chaos anrichten denn eine Lösung sein, da MySQL ja gleiche Schlüssel aussortieren soll.
Viele Grüße
Tobias
Am 01.08.2012 18:43, schrieb Ronny Seffner:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Vielleicht… Aber vielleicht solltest Du auch nicht jedesmal mysql neu starten. Wenn Du schon sed verwendest, kannst Du auch gleich alles mit sed machen:
(cat prepare-insert.sql
sed -e \ "s"\ "%"\ "([0-9]+)[.:]([0-9]+)[.:]([0-9]+)[ \t]+([0-9:.]+)[ \t]+([0-9]*)[,.]([0-9]*)"\ "%"\ "INSERT INTO `test` (`timestamp`, `value`) "\ "VALUES ('\3-\2-\1 \4','\5.\6')"\ " ON DUPLICATE KEY "\ "UPDATE `timestamp`= '\3-\2-\1 \4', `value` ='\5.\6';"\ "%" \ "$FILE" ;
cat after-insert.sql ) | mysql machines -uroot -p$SQLPWD
Du kannst auch awk mit printf benutzen da kannst Du Dir auch jede Zeile mit einem ähnlichen oder demselben (ich weiß immer nicht wann wo Backlashes gesetzt werden müssen) regulären Ausdruck auseinandernehmen lassen. Du kannst auch das „Skript“ in eine Extradatei speichern.
Die beiden Dateien kannst Du dann noch benutzen um Zeugs drumrum zu basteln. Z.B. die Indizes während der Inserts ausschalten. Beim Einschalten werden sie dann aktualisiert. Das macht mysqldump auch so.
Tobias
Hallo Ronny,
mein Weg wäre das mal mit dem Kommandozeilenprogramm mysqlimport zu testen. Da du eine Text-Datei hast kannst du mysqlimport ja weiß machen, dass es eine CSV-Datei wäre. Entweder du gibst dem Programm den Feldtrenner <leerzeichen> mit oder du packst per awk/sed/... an die passende Stelle ein Semmelkorn/Komma. Dann kannst du die Daten in eine temporäre Tabelle importieren und per SQL-Magic DATE_FORMAT, TRIM, ON DUPLICATE KEY UPDATE, ... (siehe auch Andreas Tipp mit der ID wegen dem letzten Wert) die Daten in die richtige Tabelle kopieren. Die temporäre Tabelle würde ich nehmen um bei evtl. Fehlern die Daten bereinigen zu können.
Gruß,
Falk
Zitat von Ronny Seffner ronny@seffner.de:
Hallo,
ich habe regelmäßig (täglich) ein paar Daten aus einer proprietären Software anfallenden Textdateien in eine MySQL Datenbank zu packen. Als Admin kann ich mir mit BASH und MySQL ein wenig helfen, Programmierer bin ich damit nicht. Nun fällt erschreckend auf, wie langsam das Ganze ist und ich hoffe man kann mir hier helfen (im Zweifel dann auch bezahlt). Aber erstmal zu den Fakten:
So sieht die Datei aus (es sind dann Dutzende dieser Dateien, mit je Tausenden Zeilen):
03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:01:44 0,04413 03.05.2012 00:01:45 0,42134 03.05.2012 00:01:46 0,39369 03.05.2012 00:01:47 0,44423 03.05.2012 00:01:48 0,54073 03.05.2012 00:01:49 0,56819 03.05.2012 00:01:50 0,61855 ...
Sind mehrere Zeitstempel identisch, soll der _LETZTE_ Wert genommen werden.
Das ist meine Tabelle:
CREATE TABLE IF NOT EXISTS `test` ( `timestamp` datetime NOT NULL, `value` decimal(10,5) NOT NULL, PRIMARY KEY (`timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Ich vermute schon hier unüberlegt strukturiert zu haben. Ich möchte den Zeitstempel aber weiterverarbeitbar haben, da macht doch ein MySQL Format Sinn, auch wenn völliger Käse geliefert wird. Und weil ich ja nur den Letzten Eintrag bei identischen Zeitstempeln merken will, habe ich mich für "UPSERT" entschieden, also das Feld auf PRIMARY KEY gesetzt.
Und hier nun mein primitives Skript, welches auf einer Core i3 3GHz Maschine mit 8gb RAM so 20 bis 30 Zeilen in der Sekunde ins MySQL schickt:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Mit freundlichen Grüßen / Kind regards Ronny Seffner -- Ronny Seffner | Alter Viehweg 1 | 01665 Triebischtal
www.seffner.de | ronny@seffner.de | +49 35245 72950
Lug-dd maillist - Lug-dd@mailman.schlittermann.de https://ssl.schlittermann.de/mailman/listinfo/lug-dd
Hallo Falk,
mysqlimport kannte ich noch nicht, kann aber Sinn machen, gerade, wenn ich mir mit Tobias' sed Hilfe die Textdatei so gestalte, dass das Format stimmt. Wenn ich die sed-Hürde genommen habe probiere ich Deine Idee mal.
Mit freundlichen Grüßen / Kind regards Ronny Seffner
Hallo,
Ronny Seffner wrote:
Hallo,
ich habe regelmäßig (täglich) ein paar Daten aus einer proprietären Software anfallenden Textdateien in eine MySQL Datenbank zu packen. Als Admin kann ich mir mit BASH und MySQL ein wenig helfen, Programmierer bin ich damit nicht. Nun fällt erschreckend auf, wie langsam das Ganze ist und ich hoffe man kann mir hier helfen (im Zweifel dann auch bezahlt). Aber erstmal zu den Fakten:
So sieht die Datei aus (es sind dann Dutzende dieser Dateien, mit je Tausenden Zeilen):
03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:25 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:00:26 0,03296 03.05.2012 00:01:44 0,04413 03.05.2012 00:01:45 0,42134 03.05.2012 00:01:46 0,39369 03.05.2012 00:01:47 0,44423 03.05.2012 00:01:48 0,54073 03.05.2012 00:01:49 0,56819 03.05.2012 00:01:50 0,61855
OK, also der Timestamp mit einem Leerzeichen getrennt, und dann mit einem Tab getrennt davon der eigentliche Datensatz.
Sind mehrere Zeitstempel identisch, soll der _LETZTE_ Wert genommen werden.
Das ruft ein wenig nach einem Hash und damit eher nach perl als bash...
Also zum Beispiel:
#!/usr/bin/perl use strict; # das DBI ist für später nötig use DBI; my $data; # das Datenfile wird per Kommandozeile an das Skript übergeben my $datafile = $ARGV[0]; die "Usage: $0 <datafile>" unless -f $datafile; # jetzt das Dateinfile einlesen open(IN,"<",$datafile) or die "Can't open $datafile for reading: $!"; while (my $line = <IN>) { chomp($line); my ($timestamp, $value) = split /\t/,$line; $data->{$timestamp} = $value; } close(IN);
# und ab jetzt sind alle Daten in einem Hash, auf den die # Referenz $data existiert, und es ist auch immer nur der letzte # Eintrag pro Zeitstempel pro Datei im Hash, da ein doppelter # Eintrag immer vom nächsten überschrieben wird
Das ist meine Tabelle:
CREATE TABLE IF NOT EXISTS `test` ( `timestamp` datetime NOT NULL, `value` decimal(10,5) NOT NULL, PRIMARY KEY (`timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Ich vermute schon hier unüberlegt strukturiert zu haben.
Kommt darauf an, erstmal sieht es naheliegend aus.
Und hier nun mein primitives Skript, welches auf einer Core i3 3GHz Maschine mit 8gb RAM so 20 bis 30 Zeilen in der Sekunde ins MySQL schickt:
cat "$FILE" | while read ENTRY; do YEAR=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $3}'` MONTH=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $2}'` DAY=`echo "$ENTRY" | awk '{print $1}' | awk -F. '{print $1}'` TIMESTAMP=`echo "$ENTRY" | awk '{print $2}'` NEWDATE="$YEAR-$MONTH-$DAY $TIMESTAMP" # VALUE=`echo "$ENTRY" | awk '{ print $3 }' | sed -e 's/,/./g'` VALUE=`echo "$ENTRY" | awk '{ print $3 }'` SQL="INSERT INTO `test` (`timestamp`, `value`) VALUES ('$NEWDATE', '$VALUE') ON DUPLICATE KEY UPDATE `timestamp`= '$NEWDATE', value = '$VALUE';" mysql machines -uroot -p$SQLPWD -e "$SQL" done
Der Fehler hier ist, dass du pro SQL-Statement mysql neu aufrufst, also jedesmal den Overhead von Verbindungsaufbau, Userauthentifizierung und Verbindungsabbau in Kauf nimmst. Den solltest du minimieren. Die paar tausend sed und awk würden sich in perl auch erübrigen. Bei obigem perl-Schnippsel am Anfang würde das also zum Beispiel so weitergehen:
my @sqlstatements; foreach my $timestamp (keys %$data) { my ($date, $time) = split / /, $timestamp; my ($day, $month, $year) = split /./, $date; my $newtimestamp = "${year}-${month}-$day $time"; my $value = $data->{$timestamp}; $value =~ s/,/./; push @sqlstatements, "INSERT INTO `test` (`timestamp`, `value`) VALUES ('$newtimestamp', '$value') ON DUPLICATE KEY UPDATE `timestamp`= '$newtimestamp', value = '$value';"; }
# die SQL-Statements werden also hier erstmal in ein Array # gepackt. Das kann dann nachher am Stück abgearbeitet werden. # Alles nur der übersichtlichkeit halber für diese Mail, # normalerweise könnte man in der Schleife schon das Statement # ausführen
Ich denke hier jedes Mal mehrere awk und sed zu starten nur um das unglückliche Datumsformat zu verändern muss nicht sein. Leider kann ich kein perl, das soll ja Wunder wirken ...
Siehe oben, alles in ein paar Variablen packen und gut, der Overhead für neue Prozesse (fork() und exec()) entfällt also erstmal.
Aber auch ohne die Datumskonvertierung ist das alles nicht wirklich schnell. Die my.cnf ist noch UBUNTU Standard und vielleicht sind so viele kleine INSERTS auch gar nicht wirklich gut?
Das Problem sind die vielen einzelen "Verbindung aufbauen, User authentifizieren, am Ende Verbindung abbauen" für jedes Statement.
Daher jetzt also der letzte Schritt im perl-Skript, die Kommunikation mit der Datenbank:
my $database = "machines"; my $host = "localhost"; my $port = "3128"; my $user = "root"; my $password = "wasauchimmer"; my $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; my $dbh = DBI->connect($dsn, $user, $password); foreach my $sql (@sqlstatements) { $dbh->do($sql); } $dbh->disconnect();
# Fertig. :-)
Wenn du bei bash statt perl bleiben willst, dann gib in deiner Schleife alle SQL-Statements nur aus (mit echo) und pipe das Ganze in ein einzelnes mysql - das beschleunigt die Sache deutlich.
Wenn du wirklich viele Statements hast, hilft es noch, prepared Statements zu verwenden. Aber das ist ein Thema für jemdanden, der mehr mit mysql macht als ich :-)
Ciao, Thomas
Hallo Thomas,
Dein ausführlich erklärter Code wird mir als Einstig in perl dienen. Momentan liest sich das für mich wie bash ohne die ganzen Verrenkungen (kann aber auch daran liegen, dass ich allein bash nicht annähernd perfekt beherrsche ;-).
Ich glaube mit perl muss ich mich mal beschäftigen.
my $port = "3128";
Der squid kann jetzt wohl auch MySQL cachen ;-)
Mit freundlichen Grüßen / Kind regards Ronny Seffner
Hallo Ronny,
Ronny Seffner wrote:
Hallo Thomas,
Dein ausführlich erklärter Code wird mir als Einstig in perl dienen. Momentan liest sich das für mich wie bash ohne die ganzen Verrenkungen (kann aber auch daran liegen, dass ich allein bash nicht annähernd perfekt beherrsche ;-).
Nein, das liegt daran, dass man in perl manche Verrenkungen der bash einfach nicht braucht, vor allem in der Behandlung von Zeichenketten :-)
Ich glaube mit perl muss ich mich mal beschäftigen.
Ich kann das Buch "Einführung in perl" als Einstieg empfehlen, nett geschrieben und erklärt so die grundsätzlichen Dinge. Später dann das "Perl Kochbuch", das für viele Alltagsprobleme Lösungsansätze liefert. Beide bei O'Reilly erschienen.
Ansonsten gibt's hier noch nen englischen Einstieg: http://www.perl.org/books/beginning-perl/
Ferner braucht man bei Verwendung eines Perlmoduls üblicherweise noch ~> perldoc <modulname> und für Funktionen noch ~> perldoc -f <funktionsname> als Referenz. Eher zum Einstieg ist wieder ~> perldoc perlintro gedacht, und dann gibt's da noch einiges mehr an Doku, zum Beispiel ~> perldoc perlre für reguläre Ausdrücke oder ~> perldoc perlop für die vielen Operatoren, die es so gibt.
my $port = "3128";
Der squid kann jetzt wohl auch MySQL cachen ;-)
Oops, das war etwas unglücklich von mir. Keine Ahnung, wie ich darauf kam jetzt...
Ciao, Thomas
Hallo,
Thomas Köhler wrote:
Ich kann das Buch "Einführung in perl" als Einstieg empfehlen, nett geschrieben und erklärt so die grundsätzlichen Dinge. Später dann das "Perl Kochbuch", das für viele Alltagsprobleme Lösungsansätze liefert. Beide bei O'Reilly erschienen.
Ersteres hier: http://www.oreilly.de/catalog/learnperl6ger/ oder als ebook-Version: http://www.oreilly.de/catalog/pdf_learnperl6ger/
Letzteres hier als ebook-Version, die Version auf Papier ist vergriffen: http://www.oreilly.de/catalog/pdf_perlckbk2ger/
Ciao, Thomas
lug-dd@mailman.schlittermann.de