phpundmysql.de

Archiv der Kategorie ‘MySQL’

Es nieselt wieder: Drizzle 7.1

Dienstag, April 10th, 2012

UPSERT in MySQL: INSERT … ON DUPLICATE KEY UPDATE

Mittwoch, März 28th, 2012

Wer die Feinheiten der Syntax kennt, kann sich ihne viel Arbeit oft selbst aus der Patsche helfen. Das INSERT ... ON DUPLICATE KEY UPDATE, kurz UPSERT, ist so ein Fall. Ohne UPSERT hat man in der Regel zwei Wege herauszufinden, ob man beim Befüllen der Datenbank ein INSERT oder ein UPDATE benötigt:

1. Ein SELECT absetzen, das genau nach der Kombination von Schlüsselattributen sucht, die man einfügen möchte. Liefert das SELECT einen Datensatz zurück, dann macht man ein UPDATE, sonst ein INSERT. Oder
2. Gleich ein INSERT absetzen und prüfen, ob ein ERROR 1062 (23000): Duplicate entry... zurückkommt. Wenn ja, dann ein UPDATE hinterherschieben.

Beides erzeugt wesentlich zu viel Aufwand. Nehmen wir beispielsweise folgende MySQL-Tabelle

CREATE TABLE upsert (
id INT(11) NOT NULL,
updated_on TIMESTAMP NOT NULL,
value VARCHAR(100) NOT NULL,
PRIMARY KEY (id));

Die Tabelle ist sehr einfach strukturiert: id dient als Primärschlüssel (ohne AUTO INCREMENT), updated_on hält den Zeitpunkt der letzten Änderung fest und value speichert irgendeinen Wert. Mit

INSERT INTO upsert (id, updated_on, value) VALUES (1, CURRENT_TIMESTAMP, 'abc');

wird ein erster Datensatz eingefügt. Wird dasselbe Statement noch einmal abgesetzt, kommt es zum oben beschriebenen Fehler 1062. Ist der Tabelleninhalt beim Einfügen nicht bekannt, hilft uns das UPSERT weiter:

INSERT INTO upsert (id, updated_on, value) VALUES (1, CURRENT_TIMESTAMP, 'abc')
ON DUPLICATE KEY UPDATE updated_on = CURRENT_TIMESTAMP, value = 'abc';

Die Schlüsselattribute müssen im zweiten Teil des Befehls natürlich nicht mit angegeben werden, denn bei dem alternativen UPDATE verändern sie sich naturgemäß nicht. Anstatt im UPDATE dieselben Parameter wie im INSERT zu nutzen, kann man auch andere Werte zuweisen.
Noch einfacher ist natürlich, wenn man die Parameter (CURRENT_TIMESTAMP und 'abc') nicht zweimal in seinen Befehl einbauen muss. Das hilft besonders bei der Verwendung von Prepared Statements. Diesen Komfort erreicht man durch die Verwendung des VALUES() innerhalb des UPDATE-Teils. Damit hat man Zugriff auf die Werte, die man im vorderen Teil des Befehls festgelegt hat.

INSERT INTO upsert (id, updated_on, value) VALUES (1, CURRENT_TIMESTAMP, 'abc')
ON DUPLICATE KEY UPDATE updated_on = VALUES(updated_on), value = VALUES(value);

2 ROWS AFFECTED???

Bei genauerer Betrachtung fällt noch eine Eigenheit des UPSERT auf. Auch wenn sich nur ein Datensatz in der Tabelle befindet, vermeldet MySQL die erfolgreiche Ausführung des UPSERT mit

Query OK, 2 rows affected (0.00 sec)

Das ist kein Fehler, sondern ein gewünschtes und dokumentiertes Verhalten. Wird bei einem UPSERT eine Zeile eingefügt, meldet MySQL 1 row affected, muss stattdessen eine Aktualisierung vorgenommen werden, sind es eben 2.

NoSQL: HANDLER Statement in MariaDB

Dienstag, Februar 21st, 2012

Ende 2010 haben wir über HandlerSocket für MySQL berichtet. Mit dem Plugin lassen sich Teile der MySQL Architektur umgehen, was direkten und somit deutlich schnelleren Zugriff auf die Daten erlaubt.

MariaDB, der MySQL Fork von Monty Widenius, ist zwar immer noch so nah am Original, damit auch Handlersocket darin läuft, bietet allerdings eigene Funktionen an, um das gleiche ohne Plugin zu erreichen: HANDLER Kommandos. Darunter sind alternative SQL Kommandos zu SELECTs zu verstehen, die dem Server sagen: Ich will Datensatz xy im Index abc suchen. Der explizite Verweis auf den Index-Eintrag beschleunigt die Abfrage natürlich.

Index Scans
Im Folgenden nutzen wir die Beispieldatenbank employees-db aus dem Launchpad. Anstatt

SELECT * FROM employees WHERE emp_no=100000;

steht dann

HANDLER employees OPEN;
HANDLER employees READ unique_idx=(100000);
HANDLER employees CLOSE;

Statt einem Statement braucht ein Handler zusätzliche OPEN und CLOSE Anweisungen. Der Vorteil des Handlers wird hieraus nicht sichtbar. Das Ergebnis ist nämlich das gleiche wie im obigen SELECT: Es werden alle Spalten des Datensatzes zurückgegeben, in dem die ID gleich 100000 ist. Allerdings erlaubt der Handler, den Index zu durchwandern:

HANDLER employees OPEN;
HANDLER employees READ unique_idx=(100000);
HANDLER employees READ unique_idx NEXT;
HANDLER employees READ unique_idx PREV;
HANDLER employees READ unique_idx LAST;
HANDLER employees CLOSE;

Es lassen sich also mehrere Abfragen innerhalb des Handlers abfackeln. Vorsicht ist allerdings geboten, wenn es um den Index geht. Im obigen Beispiel wird unique_idx verwendet, der in der employees-db nicht von Vornherein angelegt ist. Wir hatten Schwierigkeiten, den Handler mit dem Primary Key zu verwenden, der auch PRIMARY heißt. Hier scheint es einen Konlikt mit dem reservierten Wort zu geben.

Table Scans
Ein Handler muss nicht zwangsläufig auf einen Index verweisen. Es lassen sich auch ganze einfache Tabellen durchlaufen. In den Statements entfallen dann logischerweise nur die Index-Zusätze, der Rest bleibt identisch:

HANDLER employees OPEN;
HANDLER employees READ FIRST;
HANDLER employees READ NEXT;
HANDLER employees READ PREV;
HANDLER employees READ LAST;
HANDLER employees CLOSE;

Pagination
Handler lassen sich wunderbar dafür einsetzen, Ergebnisse aus Datenbanktabellen seitenweise anzuzeigen. Da sie die Ordnung aus dem Index ausnutzen, können Sie in Verbindung mit LIMIT gleich große Mengen Daten z.B. für eine tabellarisch Darstellung liefern:

HANDLER employees OPEN;
HANDLER employees READ unique_idx=(100000) LIMIT 10;
HANDLER employees READ unique_idx NEXT LIMIT 10;
HANDLER employees READ unique_idx LAST LIMIT 10;
HANDLER employees CLOSE;

Partition Exchange mit MySQL 5.6

Donnerstag, Februar 2nd, 2012

Das Problem, welches durch Partition Exchange gelöst wird, sollte jedem bekannt sein, der schon einmal eine größere Datenmenge in Null-Komma-Nix in eine viel abgefragte Datenbank schaufeln musste: Zugreifende Anwender sollen von dem Ladevorgang nichts mitbekommen, solange er nicht vollständig abgeschlossen ist - weder soll die Performance der Zugriffe maßgeblich beeinträchtigt werden, noch sollen die bereits geladenen Teile in den Anfrageergebnissen auftauchen.

Das Vorgehen beim Partition Exchange ist denkbar einfach. Die Daten werden nicht gleich in die partitioinierte Zieltabelle geladen, sondern in eine strukturgleiche unabhängige Tabelle. Der Ladevorgang wird daurch von den Abfragen entkoppelt und kann ruhig mehr Zeit in Anspruch nehmen. Ist das Laden vollständig abgeschlossen, legt man eine leere Partition in der Zieltabelle an und gibt dem Server zum Schluss das Kommando, die temporäre Tabelle und die leere Partition zu tauschen.

Zur Verdeutlichung ein Beispiel: Die Tabelle umsaetze beinhaltet Käufe eines großen Einzelhandelsgeschäfts. Jeden Tag kommen im Geschäft 50.000 Transaktionen hinzu, so dass das Datenvolumen - bei unterstellten 20 Arbeitstagen - pro Monat um 1 Mio. Datensätze steigt. Die Tabelle umsaetze dient dem Reporting und wird monatlich im Batch aktualisiert, eine Partitionierung erfolgt auf Basis des Umsatztages per PARTITION BY RANGE (datum).

CREATE TABLE IF NOT EXISTS `umsaetze` (
  `kunde_id` int(11) NOT NULL,
  `datum` date NOT NULL,
  `umsatz` decimal(10,2) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE (MONTH(datum))
(PARTITION jan VALUES LESS THAN (2) ENGINE = InnoDB,
 PARTITION feb VALUES LESS THAN (3) ENGINE = InnoDB) */;

Die Umsätze für März kommen als CSV Datei im Format

kunde_id;datum;umsatz
99141;2012-03-15;54689.4
20218;2012-03-16;38086.58
89814;2012-03-06;94034.74

Da das Format zur Umsatztabelle passt, wird nun noch eine strukturgleiche Tabelle benötigt, die sich denkbar einfach erzeugen und befüllen lässt:

CREATE TABLE maerz_umsaetze LIKE umsaetze;
ALTER TABLE maerz_umsaetze REMOVE PARTITIONING;
LOAD DATA INFILE 'mar.csv' INTO TABLE maerz_umsaetze FIELDS TERMINATED BY ';' LINES TERMINATED BY '\r\n' IGNORE 1 LINES;

Bei diesem Statement ist zu beachten, dass die Datei mar.csv im Daten-Verzeichnis der aktiven Datenbank liegen muss, ansonsten ist ein korrekter Pfad anzugeben. Da die Datei eine Kopfzeile mit Spaltennamen besitzt, wird zudem die erste Zeile ausgelassen.
Um die Daten in umsaetze zu integrieren, wird zuerst eine leere März-Partition angelegt und sofort danach mit der Tabelle maerz_umsaetze ersetzt:

ALTER TABLE umsaetze ADD PARTITION (PARTITION mar VALUES LESS THAN (4));
ALTER TABLE umsaetze EXCHANGE PARTITION mar WITH TABLE maerz_umsaetze;

Obwohl die Tabelle/Partition mehrere Tausend Datensätze umfasst, ist der Prozess beinahe sofort abgeschlossen:

Query OK, 0 rows affected (0.32 sec)

Was nützen einem die Performance Schema Tables?

Dienstag, September 13th, 2011

PHP 5.3.4 und MySQL 5.5.8 passen nicht zusammen

Samstag, Dezember 25th, 2010

Dass MySQL seine neue Version 5.5 auf den Markt wirft, ist lange sehnsüchtig erwartet worden. Dass die erste Version 5.5.8 nicht mit dem aktuellen PHP 5.3.4 verwendet werden kann, ist daher umso enttäuschender. Schuld sind die C-Header im MySQL Quellcode. Mittlerweile bestehen Patches dafür. Gopal Venkatesan liefert eine passende Beschreibung, wie die PHP und MySQL in aktueller Version doch noch zusammen passen.

HandlerSocket: MySQL wird NoSQL

Mittwoch, Dezember 22nd, 2010

Gerade ist MySQL 5.5 ins Rennen geschickt worden und soll die Performance von MySQL abermals deutlich verbessern. Diese Optimierungen können die grundlegenden Bremsen des Systems aber nicht lösen: Jede Anfrage muss durch die SQL Schicht des Datenbankmanagementsystems, um geparst und optimiert zu werden, um Locks zu setzen oder auf die Fertigstellung konkurrierender SQL Statements zu warten. Um den Neid auf die neuen Stars am Datenbankhimmel ein wenig zu schüren, fassen die Autoren des HandlerSocket Plugins zusammen: "MySQL has to do a lot of things... while (..) NoSQL do not to do."
HandlerSocket umgeht den SQL Layer, nimmt (CRUD-)Anfragen auf einem eigenen Port im MySQL Server entgegen und bedient sich der Storage Engine API, um direkt mit InnoDB Tabellen zu kommunizieren. Verständlicherweise nutzt HandlerSocket kein SQL als Anfragesprache, sondern ein eigenes rudimentäres Protokoll. Statements bestehen immer aus Einzeilern. Einfache Abfragen lassen sich auf diese Weise ohne Overhead ausführen, während komplexe Statements in SQL parallel auf herkömmlichem Wege an den MySQL Server geschickt werden.
Das Plugin steht auf Github unter https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL zum Download bereit. Für den Clientzugriff auf den Daemon existieren bereits neben PHP, Java und JavaScript auch Ruby und Python Schnittstellen.

MySQL 5.5 veröffentlicht

Donnerstag, Dezember 16th, 2010

MySQL 5.5 trägt eindeutig die Handschrift des neuen Besitzers Oracle. Auf der Featurelist stehen Punkte wie verbesserter InnoDB Support und Solaris Unterstützung ganz oben. Und so tut sich die neue Version vor allem durch Verbesserungen in der Performance und in der Skalierbarkeit hervor, basierend auf zeitgemäßer Unterstützung von Mehrkern-Systemen und einem aktualisierten Stand von InnoDB, das gleichzeitig MyISAM als Standard Storage Engine beerbt.
Eine lange Liste medienwirksamer, inhaltlicher Neuerungen wie in der Vergangenheit Trigger, Stored Procedures etc. sucht man hingegen vergebens.

Alle Neuerungen der Version findet sich in unserem Buch "PHP 5.3 und MySQL 5.5" (siehe die entsprechende Unterseite "Buch" auf dieser Domain).

InnoDB wird Default Storage Engine in MySQL 5.5

Montag, September 20th, 2010

Der MySQL Sunday, der am 19.September 2010 im Vorfeld zur Oracle OpenWorld veranstaltet wurde, brachte im wesentlichen die Gewissheit hervor, dass die nächste Version von MySQL wohl 5.5 sein wird. Zumindest hört der erste Release Candidate (RC) auf den Namen 5.5.6 und folgt damit den zahlreichen Meilensteinreleases seit der Version 5.1.

Wenig überraschend scheint unter dem Einfluss von Oracle, dass die Standard Storage Engine ab der kommenden Version InnoDB sein wird, das seit geraumer Zeit ebenfalls zu Oracle gehört. InnoDB ließ sich schon immer in MySQL nutzen und war die Engine der Wahl, wenn man referentielle Integrität oder transaktionale Garantien brauchte. Die Tatsache, dass sich viele Anwender aber zumeist mit dem eingestellten Standard MyISAM zufrieden gegeben haben, hat die Verbreitung von InnoDB im Zaum gehalten. Dass sich nun ebensoviele Anwender nicht von dem neuen Standard abwenden werden, wird InnoDB wohl einen Schub nach vorn verschaffen - zumal der wenig gewillte Administrator sich mit den Details nicht auseinander setzen muss, solange MySQL sich um die physische Verwaltung der Daten kümmert.

Dass MyISAM als Default Storage Engine ausgedient hat, zeigt sich auch bei den Forks. Montys MariaDB setzt auf Maria (künftig Aria), Drizzle hat ebenfalls zu InnoDB gewechselt. Aber auch schon zu Sun Zeiten stand MyISAM auf dem Prüfstand. Der Ersatz "Falcon" wurde jedoch nie fertig gestellt und letztlich verworfen.

MySQL Milestone 5.5.3 veröffentlicht

Donnerstag, Juli 22nd, 2010

Die Ankündigung verheißt das Übliche: weitere Verbesserungen in Sachen
Performance und Skalierbarkeit. Ein interessantes Detail findet sich
jedoch zwischen den Zeilen: "MySQL 5.5 is based on MySQL 5.4, which
won't get any further updates".