Upload mit SCP abgebrochen, was nun?

Da ich aktuell meine Server-Cluster mit den Replikations-Datenbanken bestücke, schaufel ich gigabyteweise Daten durch das Netz. Die Dateien sind gepackt bzw. gezippt größer als zwei Gigabyte, also ein nennenswerter Brocken, bei einer normalen DSL-Leitung, deren magerer Upstream an alte ISDN-Zeiten erinnert. Umso ärgerlicher, wenn der Upload der Datei abbricht, denn wie es das Schicksal will, bricht der Upload nicht nach wenigen Minuten, sondern kurz vor Schluss, nach Stunden, bei 97, 98 oder 99 Prozent ab. Es ist nicht leicht, hier ruhig vor der Konsole sitzen zu bleiben.

Bisher habe ich das Replikationsverzeichnis gezippt und dann per SCP (LINK) zum Zielserver geschickt. Leider gibt es keine Möglichkeit, einen abgebrochenen Upload per SCP wieder aufzunehmen. Abhilfe schafft der Befehl rsync. Mit diesem Befehl kann man den abgebrochenen Upload einer Datei „reparieren“. Der Syntax des Befehls lautet wie folgt:

rsync -e ssh –partial –progress quelle [benutzer@]zielrechner:zielverzeichnis/

RSync: Daten und Datenverzeichnisse unter Linux synchronisieren
RSync ist eigentlich dafür gedacht, Daten und ganze Datenverzeichnisse über ein Netzwerk zu synchronisieren. RSync prüft hierzu die Unterschiede zwischen dem Quell- und dem Zielrechner und überträgt nur die Änderungen. Dadurch, dass Rsync auch einzelne Dateien in Teilen ersetzen kann, eignet sich das Programm auch zur Wiederaufnahme abgebrochener Transfers.

Was bedeuten die Parameter von rsync?
-e ssh: Diesen Parameter sollte man aus Sicherheitsgründen beim Transfer über das Internet immer verwenden, da so eine Verschlüsselung der übertragenen Daten erreicht wird.

partial: Der Parameter „partial“ verhindert bei einem Abbruch, dass Rsync die bereits übertragenen Daten nicht einfach löscht.

Progress: Dieser Parameter ermöglicht es, den Fortschritt des Downloads zu überwachen. Für eine ausführliche Darstellung der Synchronisation kann der Parameter „-v“ hinzugezogen werden.

-P: Die Parameter Partial und Progress können mit dem Parameter „P“ zusammengefasst werden.

Komprimierung einschalten
Wenn man nicht gerade eine zip-Datei überträgt, bietet sich eine Komprimierung der übertragenen Daten an. Hierfür eignet sich der Parameter „-z“.

Eine detaillierte Beschreibung der Parameter von Rsync kann auf der Ubuntu-Seite nachgelesen werden.

rsync -Pvze ssh /home/benutzer benutzer@example.com:/backups

Trockentest: Übertragung von Daten nur Testen
Da rsync je nach Konfiguration auch Daten löschen kann, ist ein Datenverlust bei falscher Parameterübergabe nicht ausgeschlossen. Hier bietet es sich an, zuerst einen Test („dry run“) durchzuführen um zu schauen, ob die Parameter richtig gesetzt sind. Eine Simulation kann mit dem Parameter „-n“ gestartet werden.

Excel-Tabelle lesen: Auslesen einer Excel-Tabelle mit C#

Während CSV-Dateien quasi zum Standard für den Datenaustausch definierter Listen erklärt haben, sind Excel-Tabellen für den Datenaustausch eher das letzte Mittel von Privatanwendern. Zu Recht, denn zu groß ist die Gefahr, dass die verwendete Version von Excel nicht kompatibel mit der Version von Excel ist, die der Anwender nutzt. Und kann ich überhaupt voraussetzen, dass der Empfänger zahlender Kunde von Microsoft ist und Excel nutzt?
Trotz dieser Bedenken stößt man immer wieder, wenn auch selten, auf Excel-Listen, die Firmen zum Datenaustausch bereitstellen. Und da wir alle Freunde von Automationen sind, möchten wir natürlich auch automatisiert auf die Excel-Tabellen zugreifen.

Selbstredend kann C# den Zugriff auf Excel-Tabellen gewähren, schließlich nutzen wir das .NET-Framework und es ist nur eine Frage, wie man dies am schnellsten erledigt. Zuerst binden wir in unserem Visual Studio-Projekt unter VERWEISE/COM die „Microsoft Excel 11.0 Object Library“ (oder eine andere Version) ein. Als Using-Direktive nutzen wir „using Excel = Microsoft.Office.Interop.Excel;“. Schon haben wir das nötige Rüstzeug, um automatisiert auf die Excel-Tabelle zuzugreifen.

Wie wir im folgenden Code stehen können, können wir durch die Interop.Excell.dll relativ schnell Zugriff auf die Tabelle erhalten. Der Zugriff auf die Tabelle erfolgt mittels „xlApp.Workbooks.Open“. Es ist von Vorteil, zuvor das vorhandensein der Excel-Tabelle mittels „if (File.Exists(sFile))“ zu prüfen.
Neben einigen Definitionen wird der Code erst mit der Zeile „xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);“ interessant. Wir erhalten hier Zugriff auf das erste Excel-Tabellenblatt. Dann ermitteln wir die Größe/Range der Tabelle und gehen die Zeilen mittels for-Schleife durch. Um eine Zelle in einer bestimmten Spalte auszulesen, nutzen wir einfach „string sZelleSpalte1 = (string)(range.Cells[rCnt, 1] as Excel.Range).Value2;“

 

static private void ReadExcel(string sFile)
{
try
       {
       	Excel.Application xlApp;
        Excel.Workbook xlWorkBook;
        Excel.Worksheet xlWorkSheet;
        Excel.Range range;

        int rCnt = 0;
        xlApp = new Excel.ApplicationClass();
        xlWorkBook = xlApp.Workbooks.Open(sFile, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "t", false, false, 0, true, 1, 0);
        xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
        range = xlWorkSheet.UsedRange;

        //Gehe das ganze Zabellenblatt durch
        for (rCnt = 1; rCnt <= range.Rows.Count; rCnt++)
        {
            //Hier haben wir Zugriff auf jede Zeile
            if ((range.Cells[rCnt, 1] as Excel.Range).Value2 != null)
            {
                try
                {
                   string sZelleSpalte1 = (string)(range.Cells[rCnt, 1] as Excel.Range).Value2;
                   string sZelleSpalte2 = (string)(range.Cells[rCnt, 2] as Excel.Range).Value2;
                  }
                 catch { }
              }
         }

          xlWorkBook.Close(true, null, null);
          xlApp.Quit();
      }
      catch (Exception ex)
      {
         Console.WriteLine("Fehler in ReadExcel: " + ex.Message);
       }
}

MySQL Tabellen-Optimierung: Der optimale Datentyp

Hand aufs Herz: Wieviel Zeit invenstieren Sie vor dem Anlegen der ersten Datenbank-Tabelle in das optimale Tabellendesign? Ich hoffe viel, denn durch das richtige Design, die richtig gewählten Dateitypen und letztlich die richtigen Indizes lassen sich die Größe einer Datenbank minimieren und die Abfragen massiv beschleunigen.

Doch meistens ist das folgende Bild eher an der Tagesordnung. Man legt die erste Tabelle an, füllt die mit Testdaten und programmiert die Anwendung. Es kommen Spalten hinzu, manche werden gelöscht, umbenannt und viele schlichtweg vergessen. Später scheut man sich, das Datenbankdesign noch einmal zu überarbeiten, da man froh ist, dass die Anwendung läuft und man keine Zeit mehr investieren möchte.

Doch wenn die Größe der Datenbanktabelle wächst, wird man von der Realität eingeholt. Es rächt sich mit steigender Datenbankgröße, wenn man das Design vernachlässigt hat. Wer nicht ständig die programminternen Datenbankabfragen auf das Vorhandensein eines optimalen Index prüft, wird schnell Schiffbruch erleiden. Und wer zu großzügig mit Datentypen umgeht, wir schnell unnötigen Plattenplatz belegen und seine Datenbank aufgrund der Größe der Tabellen verlangsamen.

Gerade bei den numerischen Datentypen ist es unumgänglich, den „besten“ Datentyp für ein Feld zu wählen. Es gilt immer: Man wählen den kleinsten Datentyp, der für das Feld ausreicht. Wählt man einen zu großen Datentyp, belegt dieser nur unnötiger Weise Speicherplatz. Bremst unter Umständen sogar die Datenbank. Daher ist es wichtig zu wissen, welche maximale Werte ein numerischer Datentyp unter MySQL annehmen kann.

Unten aufgeführt habe ich die gängigen Datentypen (nicht vollständig!) aufgeführt und gebe Tipps für die Verwendung. Eine detaillierte Aufstellung kann in der MySQL-Doku nachgelesen werden.

  • TINYINT: Nimmt Werte zwischen 0 bis255 oder -128 bis 127 auf. Optimal für die ID von kleinen Tabellen (maximal 255 Einträge) oder Status-Felder.
  • SMALLINT: Nimmt Werte zwischen 0 bis 65.535 oder -32.768 bis 32.767 auf. Langt bereits als Datentyp für die ID-Spalte vieler Tabellen.
  • MEDIUMINT: Nimmt Werte zwischen 0 bis 16.777.215 oder -8.388.608 bis 8.838.607 auf. Optimaler Wert für ID-Felder im „kleinen“ Millionenbereich.
  • INT: Nimmt Werte zwischen 0 und 4.294.967.295 oder -2.147.438.648 und 2.147.483.647 auf. Im Gegensatz zu anderen Datenbanken ist der Integer bei MySQL schon sehr, sehr mächtig. Für viele Felder ist dieser bereits schon überdimensioniert und sollte durch einen kleineren Datentyp ersetzt werden.
  • BIGINT: Nimmt Werte zwischen 0 und 18.446.744.073.709.551.615 oder -9.223.372.036.854.775.808 und 9.223.372.036.854.775.807 auf. Gerade Umsteiger von anderen Datenbanksystemen wählen gerne das „große INT“ für die ID einer stark wachsenden Tabelle. Doch unter MySQL ist der der Integer bereits groß genug für die meisten Tabellen.

Speicherplatzbedarf von nummerischen Daten-Typen
Warum wir nur den kleinsten Datensatztyp wählen sollten, der wirklich benötigt wird, zeigt die folgende Tabelle. In dieser ist der erforderliche Speicherplatz eines Datentyp aufgeführt. Wenn wir beispielsweise für eine Spalte großzügig BIGINT (8 Byte) deklariert haben, aber ein SMALLINT (2 Byte) als Spaltentyp reichen würde, verschenken wir 6 Bytes. Pro Datensatz, wohlgemerkt! Wer als Datensatz-ID BIGINT gewählt hat und nun den Spaltentyp auf INT reduziert, spart 4 Bytes (50 Prozent) an Speicherplatz, bezogen auf das ID-Feld. Es lohnt sich also, auch laufende Tabellen zu gegebener Zeit auf die Datentypen hin zu prüfen.

  • TINYINT: 1 Byte
  • SMALLINT: 2 Byte
  • MEDIUMINT: 3 Byte
  • INTEGER: 4 Byte
  • BIGINT: 8 Byte

Update: Noch weniger Gedanken macht man sich um TEXT …

Schon mal Gedanken gemacht, wieviel Text in ein MySQL-Text-Feld passt? Viel. Sehr viel, sollte man meinen. Dem ist aber nicht so. Rund 65.000 Zeichen passen in ein Text-Feld. Nachfolgend der Speicherplatz und die maximale Textlänge von MySQL Text Feldern:

TINYTEXT 256 bytes
TEXT 65,535 bytes ~64kb
MEDIUMTEXT  16,777,215 bytes ~16MB
LONGTEXT 4,294,967,295 bytes ~4GB

Mysql trigger über mehrere datenbanken anlegen

Wir haben uns im Artikel „MySQL: Trigger einrichten – Beispiele zur Verwendung von Triggern“ mit der Anlage und Verwendung von Trigger beschäftigt. Bisher war die Grenze die eigene Datenbank. Also die Quell- und Zieltabelle, die wir mit einem Trigger beehrt haben, lagen innerhalb der eigenen Datenbank. Nun stellt sich die Frage, ob wir mit einem Trigger auch über eine Datenbankgrenze hinweg zugreifen können um Tabellen, die in unterschiedlichen Datenbanken liegen, so mit einem Trigge rzu verknüpfen.

Wir erstellen folgendes Testszenario:
Eine Datenbank „Datenbank1“ mit der Tabelle „testtabelle1“ sowie synchron die „Datenbank2“ mit der „testtabelle2“. Beide „Testtabellen“ bekommen der Einfachheit halber nur ein Feld „id“ mit dem Dateityp „integer“.

Die Tabellenstrukturen lauten wir folgt:
[Datenbank1]

CREATE TABLE IF NOT EXISTS `testtabelle1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

[Datenbank2]

CREATE TABLE IF NOT EXISTS `testtabelle2` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

mysql insert triggerWir legen nun den Trigger an. Dieser soll von datenbank1.testtabelle seine Werte in datenbank2.testtabelle2 schreiben. Daher wird der Trigger auch in der Quell-Datenbank „datenbank1“ angelegt. Der Einfachheit halber beschränken wir uns in diesem Beispiel nur auf einen INSERT-Trigger:

DROP TRIGGER IF EXISTS `insert_testtabelle2`;
DELIMITER //
CREATE TRIGGER `insert_testtabelle2` AFTER INSERT ON `testtabelle1`
FOR EACH ROW BEGIN
INSERT INTO datenbank2.testtabelle2 (id) VALUES (NEW.id);
END
//
DELIMITER ;

Wie wir sehen, sprechen wir die Ziel-Datenbank „datenbank2“ und die darin enthaltene Tabelle „testtabelle2“ einfach über „datenbank2.testtabelle2“ an.

Nun ist es Zeit, unseren datenbankübergreifenden Trigger einmal auszuprobieren. Wir fügen einen Wert in datenbank1.testtabelle1 ein:

INSERT INTO testtabelle1 (id) VALUES (1);

Wir schreiben also eine “1” in die erste Tabelle. Dieser Wert sollte nun auch in unserer datenbank2.testtabelle2 erscheinen. Wir kontrollieren es:

SELECT * FROM testtabelle2;

mysql insert triggerUnd siehe da, unser Trigger über zwei Datenbanken hinweg hat funktioniert.

Nun stellt sich die Frage, ob wir mit einem Trigger auch Tabellen auf einem anderen Datenbankserver ansprechen können. Nun, leider habe ich hier keine gute Nachrichten zu berichten. Ich habe es bis heute noch nicht geschafft, eine Verbindung zwischen zwei unterschiedlichen Datenbankservern zu erreichen. Synonyme, wie es der Microsoft SQL-Server kennt, sind MySQL leider immer noch fremd.

Datenbankabfrage Join über unterschiedliche Datenbanken
Wie bereits oben gezeigt, funktioniert das Ansprechen einer Datenbank bzw. der darin enthaltenen Tabelle über
Datenbankname.tabelle
Somit ist auch ein Join kein Problem, die richtigen Rechte vorausgesetzt. Wie bei Trigger erwähnt, habe ich bisher noch keine Möglichkeit gefunden, über mehrere Datenbankserver hinweg auf Datenbanken zuzugreifen. Im Netz kursieren einige Anleitungen, wie dies über Tricks möglich ist. Aber eine saubere Lösung sieht – leider – anders aus.

Zugriff auf MySQL-Server hinter einer Firewall

Heute müssen wir auf einen MySQL-Server zugreifen, der in einem lokalen Netz hinter einer Firewall steht. In diesem Fall ist es relativ einfach, denn das lokale Netz ist einfach ein privates Heimnetzwerk mit DSL-Anschluß. Hier ist zu beachten, dass durch den Internet-Provider jeden Tag eine neue IP-Adresse vergeben wird. Also müssen wir einen Dienst wie dyndns.org (LINK) nutzen, damit wir eine Verbindung zum Heimnetzwerk erhalten können, auch wenn wir die aktuelle IP-Adresse nicht kennen.

In unserem Beispiel hängt ein Netgear Router mit integrierter Firewall vor dem Heimnetzwerk. Damit wir von „außen“ auf den MySQL-Server kommen können, müssen wir die Firewall konfigurieren.

Port 3306 an der Firewall freigeben
Port 3306 an der Firewall freigebenWir wissen, dass der MySQL-Server auf den Port 3306 (Standard) lauscht. Man kann nächtelang mit Administratoren darüber diskutieren, ob es sinnvoll (und sicherer) ist, diesen Port freizugeben oder einen anderen Port für den Zugriff zu nutzen. Ich persönlich bin der Meinung, dass egal welcher Port offen ist, dieser natürlich eine Gefahr darstellt. Ob wir den Port 3306 oder 1234 nutzen: Ein Portsniffer wird auch diesen Port finden.
Ich lege dies in die persönliche Entscheidung, welchen Port man freigibt. In dieser Doku beschränke ich mich auf den Standard-Port von MySQL.

Bei den Netgear-Routern muss der Port über „Dienste“ zuerst angelegt werden. Wir tragen dort den Port „3306“ ein und geben ihm eine sinnvolle Bezeichnung wie beispielsweise „MySQL“.

Firewall: Port-Forwarding
MySQL Port ForwardingNun müssen wir der Firewall mitteilen, was mit Traffic am Port 3306 geschehen soll. Wir möchten, dass diese Kommunikation zum MySQL-Server geleitet wird. Hier ist wichtig, dass der MySQL-Server eine feste IP-Adresse im Netzwerk hat. Je nach Router kann hier auch eine dynamische Adresse eingegeben werden. Prinzipiell empfehle ich aber, einem Server immer eine statische Adresse zu geben.
Bei diesem Netgear-Router kann das Forwarding über „Regeln für die Firewall“ definiert werden. Mittels des Button „Hinzufügen“ fügen wir die neue Regel hinzu. Wir verknüpfen den zuvor angelegten „Dienst“ (Port 3306) mit der IP-Adresse des MySQL-Servers. Wie wir in der Hardcopy erkennen können, werden hier nur eingehende Dienste definiert. Ausgehende Dienste sind alle freigegeben. Auch hier kann man lange diskutieren, wie sinnvoll dies ist.
Es muss sichergestellt sein, dass der Port 3306 für eingehende wie ausgehende Dienste freigegeben wird.

Erster Test per Telnet
Nun könnte die Verbindung zum MySQL Server bereits funktionieren. Wir testen dies über Telnet. Bitte beachten, dass dieser Test nicht im lokalen Netzwerk, sondern von einem anderen Netzwerk aus gestartet werden muss (beispielsweise eine UMTS-Verbindung). Unter Windows startet man einem Command-Box über START/Ausführen/cmd.exe (oder Windows-R) und gibt dort „telnet“ ein. Dann „open meinhost 3306“, wobei „meinhost“ die DynDNS-Adresse ist.
Nach Bestätigen des Befehls startet der Verbindungsaufbau. Wenn eine Fehlermeldung wie „Es konnte keine Verbindung mit ….“ erscheint,  hat die Verbindung offensichtlich nicht funktioniert. Wir stattdessen ein wilder „Datenmüll“ (meist ist der Servername ersichtlich) angezeigt, hat die Verbindung funktioniert.

Freigabe („nach außen“) des MySQL-Server
Wenn die Verbindung nicht funktioniert hat, kann es daran liegen, dass der MySQL-Server noch keine Verbindungen außerhalb „localhost“ akzeptiert. Diese Einstellung macht aus Sicht der Sicherheit Sinn, muss jedoch hier geändert werden.

MySQL: Trigger einrichten – Beispiele zur Verwendung von Triggern

Über die Anwendung von Triggern in MySQL ist schon viel geschrieben worden. Auch die MySQL-Hilfe hilft wie gewohnt immer weiter. Dennoch möchte ich ein paar Worte über den Sinn und die praktische Anwendung mit Beispiele über die Trigger verlieren.

Warum Trigger?
Der Haupteinsatzpunkt von Triggern besteht, wenn man mehrere Tabellen hat, die einen Bezug miteinander haben. Wenn Tabellen normalisiert sind, verwendet man Datensätze über mehrere Tabellen hinweg. Wenn nun in der Haupttabelle beispielsweise ein Datensatz gelöscht wird, muss man immer daran denken, die Auslagerung in der Untertabelle ebenfalls zu „pflegen“. Man kann dies über die Anwendung direkt machen, was eher schlecht ist. Wer sich mit StoredProcedures auskennt, hat vielleicht eine solche geschrieben, die diese Arbeit übernimmt. Viel eleganter ist jedoch, solche Aufgaben direkt per Trigger zu erledigen.

Trigger Beispiel: Warenwirtschaft mit Produkte
In unserem Trigger Beispiel verwenden wir eine Hauptabelle „produkte“. Diese hat viele hier nicht relevante Felder. Wichtig ist das Feld „id“ und das Feld „preis“. Nun werden wir eine weitere Tabelle haben, eine sogenannte „Preishistorie“. In dieser Tabelle möchten wir später nachvollziehen können, wie sich der Preis eines bestimmten Produktes im Laufe der Zeit entwickelt hat.
In der Tabelle „preishistorie“ haben wir nur wenige Felder. Neben der Datensatz-ID („id“) haben wir den Bezug zu Tabelle produkte.id, die „produktid“. Dann den aktuellen Preis („preis“) sowie ein Datumfeld „aenderung“, in welchem wir das aktuelle Datum der Änderung speichern. Wir möchten bei einem INSERT in der Produkttabelle auch einen INSERT mit dem aktuellen (Start-)Preis in Preishistorie-Tabelle erhalten. Ferner soll bei einem DELETE auch die Tabelle Preishistorie von diesem Produkt bereinigt werden.

MySQL-Fehlermeldung 1064 bei Neuanlage Trigger
#1064 – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near “ at line X

Soviel vorweg: Diese Fehlermeldung bei der Neuanlage eines Trigger tritt auf, wenn der Trigger ohne “Delimiter” angelegt wird. Also diesen Syntax nicht vernachlässigen!

INSERT-Trigger
Wir legen zuerst einen Insert-Trigger an. Dieser wird ausgelöst, wenn ein INSERT auf unsere Tabelle produkte erfolgt IST (AFTER). Dies ist deshalb sehr wichtig, da wir die automatisch eingefügte id des Datensatzes benötigen, der hier per „NEW.id“ in die Spalte „productid“ übergeben wird.

Bei jedem INSERT wird nun der aktuelle Preis mit aktuellem Datum in die Tabelle Preishistorie geschrieben.

delimiter |

CREATE TRIGGER insert_preishistorie
      AFTER INSERT
      ON produkte
      FOR EACH ROW BEGIN
      INSERT INTO preishistorie (productid, preis,aenderung) VALUES (NEW.id, NEW.preis,NOW());
    END;
|
delimiter ;

DELETE-Trigger
Wenn ein Produkt in unserer Haupttabelle gelöscht werden soll, sollen auch alle Einträge in der Preishistorie gelöscht werden. Dies erreichen wir über den DELETE-Trigger:

delimiter |

CREATE TRIGGER delete_preishistorie
      AFTER DELETE
      ON produkte
      FOR EACH ROW BEGIN
      DELETE FROM preishistorie WHERE productid=OLD.id;
    END;
|
delimiter ;

UPDATE-Trigger

trigger update
Doppelte Einträge beim Update/Insert-Trigger

Dieser Trigger stellt uns, wenn man die Syntax verstanden hat, auch nicht weiter vor ein Problem. Hier ist jedoch anzumerken, dass dieser Trigger bei jedem Update ausgelöst wird. Dies würde bedeuten, dass wir den Preis auch in unsere Historie aufnehmen würden, wenn ein anderes Feld anstatt des Preises sich ändert. Aus diesem Grund müssen wir prüfen, ob sich der Preis auch wirklich geändert hat. Hier wird auch spätestens der Unterschied zwischen OLD.preis und NEW.preis deutlich.

delimiter |

CREATE TRIGGER update_preishistorie
      AFTER UPDATE
      ON produkte
      FOR EACH ROW BEGIN
            IF NEW.preis != OLD.preis THEN
                  INSERT INTO preishistorie (productid, preis,aenderung) VALUES (NEW.id, NEW.preis,NOW());
            END IF;
    END;
|
delimiter ;

Trigger löschen
Um einen Trigger zu löschen, benutzt man einfach untenstehende Anweisung:
DROP TRIGGER update_preishistorie

Probleme bei der Synchronisation des Kalenders Android-Hotmail HTC Desire

Das HTC Desire mit seinem Android Betriebsystem ist eine prima Smartphone, ohne Frage. Doch wer seinen Kalender online synchronisieren lässt, wird es leider des öfteren erleben, dass der Kalender plötzlich alle Termine „vergisst“ und von nun an die Kalender-Synchronisation mit der Google- oder Hotmail-Cloud fehlschlägt. Es liegt nicht am stürmischen Wetter, sondern meist an einem Update von Android. Zumindest bei den mir vorliegenden Fällen ist die fehlerhafte Kalender-Synchronisation nach einem Android-Systemupdate aufgetreten und konnte als Fehlerquelle auch reproduziert werden. Interessanter Weise war die Mail-Synchronisation in keinem Fall davon betroffen; jedoch nicht auszuschließen, dass auch beim ein oder anderen Smartphone-Nutzer auch die Mail-Synchronisation fehlschlägt.
Glücklicher Weise handelt es sich nicht um einen Fehler im speziellen Android-Update, sondern wohl um ein Problem im Betriebsystem selbst. Dieses Problem bereitet vielen Android-Nutzern Probleme, wie sich auch in vielen Foren nachlesen lässt. Mit wenigen – wenn auch ärgerlichen – Schritten kann die Kalender-Synchronisation wieder aktiviert werden.

  1. Zuerst ist natürlich der Kalender manuell zur Synchronisation zu bewegen. Ich gehe davon aus, dass diesen Schritt bereits jeder genervte Smartphone-Nutzer vergeblich erledigt hat.
  2. Wenn dies nicht zu einem Erfolg führt, ist unter EINSTELLUNGEN/ ANWENDUNGEN / ANWENDUNG VERWALTEN der Kalenderspeicher zu löschen. Wenn dieser Schritt erledigt wurde, konnte in manchen Fällen durch das manuelle Anstoßen der Kalendersynchronisation das Smartphone wieder zur Synchronisation mit der Cloud „überredet“ werden. Wenn nicht, muss mit härteren Bandagen gekämpft werden.
  3. Wenn die ersten beiden Schritte kein Erfolg gebracht haben, müssen Sie leider die Verknüpfung zur Kalender-Cloud gelöscht und wiederhergestellt werden. Dies war leider meist die einzige Möglichkeit, nach einem Android-Update wieder eine Verbindung zum Google oder Hotmail-Kalender zu erhalten.
  4. Löschen Sie unter „ANWENDUNGEN VERWALTEN“ (Tabreiter „Alle Anwendungen“) die Verbindung zu Ihrem Online-Kalender. Wenn Sie beispielsweise ein Mail-Programm wie „Improved EMail“ zur Synchronisation nutzen, wählen Sie dieses an und klicken Sie auf „Daten löschen“. Achtung: Sie löschen hier nicht die Anwendung, aber alle Daten, die die Anwendung gespeichert hat. Dies bedeutet, dass Sie danach weder Mails noch Verbindungsdaten zu Ihrer Cloud mehr haben.
  5. Nachdem Sie die Verbindungsdaten so gelöscht haben, starten Sie Ihre Mail-App. Die App wird sie „jungfräulich“ mit der Frage nach den Benutzerdaten begrüßen. Geben Sie diese ein und starten die Synchronisation neu. Im Artikel „Hotmail Server Einstellungen für HTC Desire“ können Sie die Standard-Parameter für einen Exchange oder POP3-Verbindung zum Hotmail-Account entnehmen.
  6. Vergessen Sie in keinem Fall, auch der Mailanwendung mitzuteilen, dass Sie für die Kalender-Synchronisation zuständig ist.
  7. Nun sollte die Synchronisation neben den aktuellen Mails auch den Android Kalender wieder mit aktuellen Kalender-Daten füllen. Bis zum nächsten Android-Update sollten Sie nun wieder einen verlässliche Kalender-Synchronistation haben.

MySQL-Tabelle per CronJob automatisch optimieren

Jede Datenbank, die untertags durch viele INSERT, DELETE oder UPDATE-Statements drangsaliert wird, sollte in regelmäßigen Abstanden optimiert werden. Dies kann beispielsweise mittels PHPmyAdmin per unter dem Punkt „Operatoren“ (vorher eine Tabelle auswählen), „Optimiere Tabelle“ geschehen, oder per mysql-Konsole selbst.

Warum überhaupt eine Datenbanktabelle optimieren?
Doch zuerst müssen wir uns kurz mit der Frage befassen, warum eine MySQL-Datenbanktabelle überhaupt optimiert werden muss. Nun, dies ist keine Eigenschaft, die nur MySQL betrifft. Auch Datenbanken unter Microsoft SQL Server benötigen von Zeit zu Zeit den Optimierungsbefehl. Wobei „benötigen“ eigentlich nicht richtig ist. Man muss den Befehl nicht ausführen, wenn man in Kauf nehmen kann, dass die Serverlast von Tag zu Tag steigt und die Datenbank immer langsamer wird. Der Grund liegt in der Defragmentation der Daten. Nehmen wir mal folgendes Beispiel an. Wir haben ein Feld X vom Wert tinyint. Dies ist anfänglich 0; wir updaten es auf 1. Wird das Dateisystem defragmentiert? Nein, denn ob 0 oder 1 – die Speicherplatzgröße für beide Werte ist gleich. Nehmen wir jetzt aber ein Feld „Name“ vom Typ varchar(x). Das Feld hat den Wert „Müller“. Nun machen wir ein Update auf „Müller-Lüdenscheid“. Hätten wir das Feld per Char(max) festgelegt, hätte das Datenbanksystem die maximale Länge des Feldes blockiert. Der nun längere Name, der aber unter der Maximum-Länge des Feldes liegt, hätte immer noch in den vorbereiteten Platz gepasst. Der Nachteil ist gravierender: Die Datenbank wird mit jedem Datensatz so groß wie die Definition der maximalen Datensatzlänge angibt. Auch wenn nahezu leere Felder eingefügt werden, steigt der Datenplatz unaufhaltsam an. Ein Grund, warum varchar-Felder ein Segen sind, auch wenn sie andere Probleme mit sich bringen. Weiteres über Optimize Table kann direkt in der MySQL-Doku nachgelesen werden.

Ausführung von Optimize Table
Die Ausführung des Befehls ist einfach. In einer MySQL-Shell muss lediglich
OPTIMIZE TABLE Tabellenname;
Ausgeführt werden. Zu beachten ist jedoch, dass MySQL die Tabelle während der Laufzeit von OPTIMIZE TABLE sperrt!

Während man bei einem Microsoft SQL Server – zumindest ab der Standard-Version – Wartungspläne definieren kann, die automatisch solche „Wartungen“ wie ein Optimize Table vornehmen, muss man bei MySQL auf die Möglichkeiten des Host-Systemes zurückgreifen.

Tabelle optimieren oder reparieren mittels mysqlcheck
Die meisten MySQL-Installationen bringen bereits in einer Standardinstallation das Hilfsprogramm „mysqlcheck“ mit. Der Vorteil dieses Programmes ist, dass wir damit auch auf einen laufenden SQL-Server zugreifen und wir eben das Programm per Cronjob aufrufen können. Wir ersparen uns dadurch das Schreiben von Skripts.

Einrichten eines Cronjobs für das Optimieren einer MySQL-Tabelle

  1. Wir loggen uns per SSH (putty) auf unserem Server ein
  2. Je nach Linux-Distribution warden wir mit “su” zum Root.
  3. Editieren der Crontab ( /etc/crontab) mit dem Befehl crontab -e
  4. Wir fügen folgende neue Zeile per Editor in unserer Crontab ein:
    0 1 * * * mysqlcheck -Aao –auto-repair -u root -p[password] > /dev/null
  5. Editor mit ESC, “:wq!” verlassen.

Beschreibung des Befehls:
„0 1 * * *“: Hiermit weisen wir den Cron an, jeden Tag um 1 Uhr und 0 Minuten den Befehl auszuführen. Nähere Informationen zur Steuerung eines Crons erhalten Sie im Artikel „Linux-Cronjob einrichten

mysqlcheck -Aao –auto-repair: Wir rufen damit das Programm “mysqlcheck” auf. Die Parameter können direkt in der MySQL-Doku nachgelesen werden. Die Parameter hier bewirken:

-A: Alle Datenbanken (all-databases) werden überprüft/optimiert
-a: Analysiert die Tabellen (Analyse)
-o: Optimierung (optimize) der Tabellen
-auto-repair: Wenn eine Tabelle beschädigt ist, wird sie automatisch repariert.
Mit den Parametern „-Aao –auto-repair“ erledigen wir also mehr, als ein reines „Optimize“.

Parameter „-u root -p[password]”: Hier geben wir den Datenbankbenutzer (root) und sein Passwort an

Parameter „> /dev/null”: Wir schreiben die Ausgabe des Befehls ins digitale Nirwana. Hier könnte auch eine Ausgabe in ein Logfile oder eine Mail erfolgen.

UPDATE: mysqlcheck doesn’t support multiple contradicting commands

Diese Fehlermeldung tritt bei neueren Versionen von mysqlcheck auf. Hier ist es nicht mehr möglich, mehrere Kommandos aneinanderzuhängen. Um den Fehler zu umgehen, genügt der nachfolgende Aufruf:

mysqlcheck -u root -p [PASSWORT] --auto-repair --optimize --all-databases

MySQL: Binärlog sicher automatisch löschen

Wenn ein MySQL-Datenbankserver per Replikation einige Tage in Betrieb ist, häufigen sich die Binär-Logs im MySQL-Verzeichnis. Zur Erinnerung: Ein Master-MySQL-Server speichert in seinem Binär-Log alle Transaktionen, die seine eigene Datenbank verändern. Der oder die Slave-Server greifen auf dieses Binär-Log zu und replizieren so anhand dieser Historie ihre eigene Datenbank. Ein Löschprozess findet per Standard nicht statt.

Auch wenn die Datenbank sehr klein ist, kann der Umfang der Binär-Logs einen stattlichen Umfang annehmen. Dies liegt weniger an der Größe der Datenbank – dies hat keinen primären Einfluss auf die Größe der Logfiles – sondern an der Anzahl der Insert und Updates, die eine Datenbank verändert haben. Jede einzelne Änderung findet sich im Binär-Log wieder. Wie wir in der Hardcopy hier sehen, schreibt der Beispiel-MySQL-Server seine Daten bereits in das 61. Logfile. Per Konsole erfahren wir, dass diese Logfiles bereits stolze 6,2 GB an Speicherplatz belegen, da in der mysql.cnf max_binlog_size  = 100M definiert ist; also jedes Logfile wird etwas größer als 100 MB.

Manuelle Löschung der MySQL-Binär-Logs
Prinzipiell ist das Löschen der Binär-Logs jedezeit möglich. Die Datenbank muss nicht angehalten werden und auch die Konsistenz des Master-Datenbankservers ist durch das Löschen in keinster Weise gefährdet. Warum auch? Der Master-Datenbankserver schreibt einfach fleißig sein Logfile. Er selbst stützt sich nicht auf die Logfiles. Wohl aber die Slave-Server. Wenn wir also Logfiles auf dem Master löschen, die ein Slave noch nicht verarbeitet hat, droht eine Inkonsistenz der Replikation. Also müssen wir jeden Slave fragen, mit welchem Logfile er gerade beschäftigt ist. Das älteste Logfile, das JEDER Slave bereits vollständig abgearbeitet hat, ist demnach das neueste Logfile, das gelöscht werden darf.

Ermittlung des aktiven Replikation-Logfiles
Wir loggen uns also auf jedem Slave-Server über die MySQL-Konsole ein und fragen nach dem aktiven Logfile:

# mysql –u root –p
mysql > SHOW SLAVE STATUS G

Über den Befehl SHOW SLAVE STATUS erfahren wir, welches das aktuelle Master-Logfile (Master_Log_File) und das Relay_Master_Log_File ist. Auf unserem Beispiel-Server würde im Statusbricht „mysql-bin.000061“ auftauchen. Wenn alle Slaves aktuell am 61. Logfile arbeiten, können die 60 Logfiles davor also gelöscht werden.

Entfernung der MySQL-Binär-Logfiles
Der folgende Befehl löscht die Binär-Files unwiderruflich. Ängstliche Administratoren können gerne Sicherheitskopien der zu löschenden Logfiles anfertigen. Sollte man sich einmal bei dem folgenden Befehl „verhauen“ und eine Inkonsistenz der Replikation erreichen, dann ist dies nicht der Untergang des Abendlandes. Man fertigt dann eine aktuelle Sicherung der Datenbank auf dem Master an, installiert diese auf dem Slave und startet dort die Replikation zu diesem Zeitpunkt neu.

Da wir keine Angst haben, löschen wir also die Logfiles. Zu beachten gilt: Die Angabe der Logfile-Nummer im folgenden Befehl ist exklusive; es werden also nur Logfiles BIS ZU DIESEM gelöscht. Wir können in unserem Fall alle 60 Logfiles löschen. Der Befehl dafür lautet also:

PURGE BINARY LOGS TO `mysql-bin.000061`

Automatisierte Löschung der Binär-Logs
Bei der routinemäßigen Überprüfung der Server kann der Administrator sicherlich die Binär-Files so löschen. Allerdings bietet MySQL auch die automatisierte Löschung an. Allerdings kann darüber nur ein Zeitraum in Tagen definiert werden. Ob der oder die Slave-Server auf dem aktuellen Stand sind, wird nicht überprüft. Aber auch hier gilt das gleiche wie bei der manuellen Löschung: Wenn einmal der Fall eintritt, dass die Inkonsistenz eintritt, muss die Datenbank auf dem Slave neu eingespielt werden. Ärgerlich, da mit Offline-Zeiten zu rechnen ist, aber kein Untergangs-Szenario.

In der MySQL-Konfigurationsdatei my.conf (/etc/mysql/my.cnf) finden wir den Eintrag “expire_logs_days=0“. Die „0“ setzt das Löschen außer Kraft. Wir setzten diesen Wert nun auf einen vernünftigen Wert. Ob

expire_logs_days=7

oder

expire_logs_days=30

die richtigen Werte sind, muss jeder MySQL-Datenbank-Administrator für sich selbst entscheiden. Da die Replikation der Slaves vom Administrator sowie ständig überwacht werden muss, dürfte ein so langes „Hinterherhinken“ eines Slaves sowieso nicht toleriert werden. In vielen Installationen sind heute bereits 10 Tage per default hiterlegt:

server-id        = 1
log_bin            = /var/log/mysql/mysql-bin.log
expire_logs_days    = 10
max_binlog_size         = 100M

Gimp: Bilder umwandeln von RGB nach CMYK

Die freie Grafikanwendung GIMP läuft sicher der professionellen Lösung Photoshop den Rang ab. Für semiprofessionelle Anwender ist GIMP sicherlich eine Alternative, denn die Grafikanwendung ist kostenfrei. In der Bedienung geben sich beide Anwendungen ebenfalls nichts: Ohne stundelanges Beschäftigen mit der Anwendung kommt man zu keinem Ergebnis. Erst wenn es um professionelle Lösungen für die Druckvorstufe geht, merkt man, dass GIMP langsam die Luft ausgeht. Es ist zu hoffen, dass die Entwickler hier noch nachbessern.

Wenn es um den CMYK-Farbraum geht, muss man Photoshop eindeutig den Vortritt lassen. Doch mit einigen „Tricks“ und Add-Ons kommt man auch mit GIMP weiter. So stellt einem beispielsweise das Umwandeln von Bildern von RGB nach CMYK erst einmal vor ein Problem, denn Gimp kann dies von Haus aus aktuell noch nicht. Für die Druckvorstufe werden jedoch Bilder benötigt, die bereits in die Druck-Farbanteile zerlegt sind. Um es gleich vorweg zu nehmen: Wenn man eine Druckerei hat, die RGB-Dateien entgegennimmt und diese Arbeit für einen geringen Aufpreis übernimmt, sollte man dies einen Spezialisten erledigen lassen. Denn am heimischen Monitor kann man nie sicher sein, dass eine Farbe später auch so gedruckt erscheint, wie gewünscht. Die Spezialisten in der Druckerei haben die Erfahrung und die technischen Möglichkeiten, dies schon im Vorfeld abzuschätzen und zu korrigieren.

Wer die Konvertierung von RGB nach CMYK mittels Gimp selbst vornehmen möchte (auch ich mache dies über diesen Weg – trotz der negativen Vorrede), muss zuerst sein Gimp erweitern. Die einzelnen Schritte zur Installation, Konfiguration und Farb-Konvertierung sind wie folgt durchzuführen:

  1. Neben der aktuellen Gimp-Version benötigen wir die aktuelle Version von „Separate+“. Zur Installation von Separate+ muss Gimp geschlossen sein.
  2. Entpacken Sie „Separate+“ in einem beliebigen Ordner (c:/tmp), denn wir benötigen nur einige Dateien aus dem Zip-Container.
  3. Die benötigten Dateien heißen „icc-colorspace.exe“, „seperate.exe“ und „seperate_import.exe“. Diese sind in der aktuellen Version im Verzeichnis „/separate+0.5.4/bin/win32_gimp2.4/“ zu finden.
  4. Kopieren Sie diese drei Dateien in das „plug-ins“-Verzeichnis von Gimp („C:\Programme\GIMP-2.0\lib\gimp\2.0\plug-ins“). Der entpackte Ordner von „Separate“ kann nun gelöscht werden.
  5. Ferner benötigen Sie Farbprofile, damit das Plug-In das Image korrekt aufarbeiten kann. Wir nutzen dazu die Adobe Farbprofile. Diese herunterladen und in einem Zielordner entpacken.
  6. Nun starten wir Gimp. Als erstes erstellen wir einen Verweis auf die Adobe Farbprofile. In Gimp wählen wir „Bearbeiten – Einstellungen“, dann in der linken Spalte „Farbverwaltung“. Im Bereich der „Farbverwaltung“ wählen wir nun im Drop-Down-Menü „CMYK-Profil“ „Farbprofil von Festplatte wählen“ aus.
  7. Im folgenden Fenster navigieren wir zum eben erstellten Ordner der „Adobe Farbprofile“ und wählen die Datei „CoatedFOGRA27.icc“ („Adobe ICC Profiles (end-user)“, „CMYK Profil“) aus. Nun steht das Profil auch unserem PlugIn „Separate+“ zur Verfügung.
  8. Öffnen Sie Ihr Bild. Achtung: Das Separate“-Plugin kann nur die gerade ausgewählte Ebene exportieren. Dies ist im Normalfall kein Problem, da die Farbseparation meist als letzter Schritt erfolgt. Vereinen Sie vorher alle Ebenen nach unten und wählen „Bild – Seperate – Seperate“. Im folgenden Fenster wählen Sie unter „Destination Color Space“ „Coated FOGRA27“ aus. Setzen Sie einen Haken bei „Make CMYK pseudo-composite“ und klicken Sie auf OK.
  9. In einem neuen GIMP-Fenster erscheint nun das von RGB nach CMYK umgewandelte Bild. Nicht erschrecken, das Bild wird sicherlich Ihnen rotstichig vorkommen. Dies ist kein Bug oder ein Fehler, sondern ein Problem der Darstellung, da das Bild nun in die vier Farbebenen (CMYK: Cyan, Magenta, Yellow und Key/Black) zerlegt wurde.
  10. Speichern Sie nun das Bild mittels „Bild – Seperate – Save“.

Nachtrag: Fehler: „Der Prozedureinsprungpunkt g_get_home_dir_utf8 wurde in der DLL libglib-2.0-0.dll nicht gefunden“

“Der Prozedureinsprungpunkt g_get_home_dir_utf8 wurde in der DLL libglib-2.0-0.dll nicht gefunden”. Wenn diese Fehlermeldung erscheint, müssen wir dem Plugin Seperate Plus eine eigene libglib-DLL hinzufügen.
“Der Prozedureinsprungpunkt g_get_home_dir_utf8 wurde in der DLL libglib-2.0-0.dll nicht gefunden”. Wenn diese Fehlermeldung erscheint, müssen wir dem Plugin Separate Plus eine eigene libglib-DLL hinzufügen.

Seit der Version 2.8.x funktioniert das Plugin nicht mehr reibungslos. Das Problem: Das Plugin benötigt eine veraltete „libglib-DLL“. Die Lösung: Wir stellen Gimp und somit dem Plugin diese DLL zur Verfügung. Die benötigten Dateien sowie das Vorgehen hat Simon Mueller in seinem Blog beschrieben.