Eine Einführung in MongoDB

Humongous [enorm, riesig, gigantisch]
Kommentare

Weiter geht es in der Reihe der NoSQL-Datenbanken. Nach CouchDB folgt nun die Einführung in eine Datenbank, die gerne in Verbindung mit CouchDB genannt wird: MongoDB. Zwischen CouchDB und MongoDB gibt es einige Parallelen, aber auch durchaus grundlegende Unterschiede.

Hinter MongoDB steht die in New York ansässige Firma 10gen , die seit 2007 das Datenbanksystem und die wichtigsten Treiber entwickelt. MongoDB steht unter der GNU AGPL v3.0, die Treiber unter der Apache-Lizenz v2.0. MongoDB ist in C++ programmiert und benutzt ein eigenes Protokoll zur Kommunikation auf dem Port 27017. Wie MySQL wird die Datenbank über Treiber für die jeweilige Sprache angesprochen (im Gegensatz zu CouchDB, bei der die Kommunikation per HTTP und REST statt findet). Die Entwicklung des PHP-Treibers erfolgt durch Kristina Chodorow. MongoDB gehört zu den schemalosen dokumentorientierten Datenbanken, also ohne eine feste Struktur der Daten innerhalb eines Datensatzes. Das Datenformat für Dokumente ist BSON (Binaray JSON), eine Erweiterung von JSON um einige binäre Typen, z. B. Datum und JavaScript. Im Gegensatz zu anderen NoSQL-Datenbanken besteht bei MongoDB die Möglichkeit zu dynamischen Abfragen. Während bei CouchDB durch Definition der Views im Vorfeld die Abfragen festgelegt sind, kann bei MongoDB jederzeit eine Ad-hoc-Abfrage ausgeführt werden. Zur Abfrage und Aggregation bei map/reduce nutzt MongoDB intern JavaScript. Ein weiterer Unterschied zu CouchDB besteht in der Art der dauerhaften Datenspeicherung. Während CouchDB jede Änderung sofort auf die Festplatte schreibt, nutzt MongoDB das „Lazy writes“-Prinzip. Alle Änderungen werden sofort im Speicher umgesetzt, in einem Intervall von ca. einer Sekunde werden die Änderungen dauerhaft auf den Massenspeicher geschrieben. Insgesamt besticht MongoDB durch einen hohen Funktionsumfang, der den Einstieg insgesamt etwas leichter als bei CouchDB macht.

BSON-Datentypen

Standard-JSON:
• String
• Integer
• Boolean
• Double
• Min/Max Key
• Array
• Timestamp
• Object
• Null

BSON-Erweiterungen:
• Date
• Object ID
• Binary Data
• Regular Expressions

Installation von MongoDB

Anlaufstelle für MongoDB ist die Seite http://mongodb.org. Unter Downloads finden Sie alle benötigten Programme und Treiber. MongoDB braucht in Windows keine Installation und kein Setup. Erzeugen Sie ein Verzeichnis Ihrer Wahl, in dem die Dienstprogramme liegen sollen, beispielsweise C:mongo164 (zum jetzigen Zeitpunkt ist die aktuelle Version 1.6.4). Extrahieren Sie die heruntergeladene Datei und kopieren das Verzeichnis bin und die drei Hinweisdateien in dieses Verzeichnis. In einem zweiten Schritt müssen Sie das Verzeichnis C:datadb erzeugen, in dem MongoDB alle Daten speichert. Da MongoDB keine Installations- oder Setuproutine hat, müssen Sie das „per Hand“ tun. Nach den zwei Schritten schauen wir uns die wichtigen Dateien aus dem Verzeichnis C:mongo164bin an:

  • mongod.exe: Der Datenbankserver. Dieses Programm muss gestartet werden und lauscht auf Port 27017 auf eingehende Anfragen
  • mongo.exe: Die interaktive Shell der Datenbank. Hier können Sie direkt mit der Datenbank kommunizieren.
  • mongostat.exe: Ein kleines Programm, das im Sekundentakt Statistiken über die ausgeführten Operationen in MongoDB liefert.

Für einen ersten kurzen Test starten Sie das Programm mongod.exe. Die MongoDB lauscht nun auf zwei Adressen: Port 2107 ist der normale Port für die Datenbankanfragen, Port 28017 ist der Port für das noch rudimentäre Webinterface. Öffnen Sie einen Browser und rufen die Adresse http://localhost:28017 auf, um eine kleine Übersicht über den Status zu sehen. Nun wollen wir mit der Datenbank reden: Starten Sie mongo.exe (in der Eingabeaufforderung oder per Doppelklick). Mit help bekommen Sie eine Liste einiger Befehle.
Unter Linux werden die Treiber über das Kommando sudo pecl install mongo installiert. Anschließend müssen Sie wie unter Windows die php.ini um die Zeile extension=mongo.so erweitern und den Webserver neu starten. Weitere Informationen finden Sie hier. Das war als Vorbereitung für die Benutzung von MongoDB unter PHP erst einmal alles.

Aufmacherbild: Asia elephant southeast Asia Thailand von Shutterstock / Urheberrecht: apiguide

[ header = Installation der PHP-Erweiterung]

Installation der PHP-Erweiterung

Windows: Hier finden Sie die aktuellen Treiber für OSX und Windows. Achten Sie beim Download auf die korrekte PHP-Version. Erzeugen Sie eine Datei mit phpinfo() und rufen Sie die auf. In der Übersicht sehen Sie die PHP-Version (5.2/5.3 etc.) und in der Zeile Compiler die Compilerversion (z. B. MSVC6), sowie Thread Safety enabled/disabled. Aus der PHP Version (5.3) und der Compilerversion (VC6), sowie der Angabe, ob threadsafe oder nicht, finden Sie die richtige Datei heraus (z. B. mongo-1.0.10-php5.3cv6.zip). Der Download beinhaltet die Datei php_mongo.dll, die Sie in das Extensions-Verzeichnis Ihrer PHP-Version kopieren. Danach erweitern Sie die php.ini um die Zeile extension=php_mongo.dll. Ein Anschließender Neustart des Webservers und ein erneuter Aufruf von phpinfo() sollte jetzt die Einträge im Abschnitt mongo anzeigen.

Linux: Unter Linux werden die Treiber über das Kommando sudo pecl install mongo installiert. Anschließend müssen Sie wie unter Windows die php.ini um die Zeile extension=mongo.so erweitern und den Webserver neustarten. Weitere Informationen finden Sie unter http://www.php.net/manual/en/mongo.installation.php. Das war als Vorbereitung für die Benutzung von MongoDB unter PHP erst einmal alles.

MongoDB Basics

An dieser Stelle möchte ich für Leser, die noch keine Erfahrung mit MongoDB haben, die Seite http://www.try.mongodb.org empfehlen. Sie enthält eine Browserversion der MongoDB Shell mit einem kleinen Tutorial, in dem Sie die Grundlagen von MongoDB kennen lernen können. Aber keine Angst, auch ohne das Tutorial werde ich Ihnen einige Grundlagen nahe bringen.
MongoDB ist in vielen Punkten etwas einfacher als z. B. CouchDB, da es versucht, das Beste aus SQL und NoSQL zu verbinden. Zunächst einmal ist auch MongoDB ein Datenbanksystem, das mehrere Datenbanken verwalten kann. Innerhalb einer Datenbank werden verschiedene Collections angelegt, in denen gleichförmige Dokumente gespeichert werden. Man kann diese Collections mit Tabellen in einer relationalen Datenbank vergleichen, allerdings ist man hier nicht an eine gleichförmige Datenstruktur gebunden.

Abb. 1: Datenbankstruktur

Angenommen, es gibt eine Collection „Produkte“, in der alle Produkte eines Shops hinterlegt sind. Jedes Dokument innerhalb dieser Collection ist ein Produkt, aber die Eigenschaften von Eiern unterscheiden sich doch erheblich von denen eines Toilettenpapiers. Und doch sind beide Produkte in der gleichen Collection enthalten. Jedes mit seinen eigenen Attributen, der Schemalosigkeit sei Dank

{"produkt": "Ei", "Gueteklasse": "A", "Haltungsart": "Freilandhaltung"}
{"produkt":"Toilettenpapier","Lagen":3}

In relationalen Datenbanken müsste man eine Produkttabelle mit allen Attributen der Produkte  als Spalten anlegen oder den Umweg über eine Produkt- und eine Attributetabelle gehen. Im Gegensatz zu CouchDB sind in MongoDB auch dynamische Abfragen möglich. Die Abfragen können über Filter oder als map/reduce-Funktionen erfolgen. Ein Dokument darf eine maximale Größe von 4 MB nicht überschreiten, und auch die Anzahl der Collections in einer Datenbank ist begrenzt. Pro Datenbank steht ein Adressraum von ca. 24 000 Namen bereit. Geht man davon aus, dass jede Collection auch einen Index benutzt, der einen eigenen Namen hat, bleibt die Möglichkeit, bis zu 12 000 Collections pro Datenbank zu erstellen. Ein Setzen des Startparameters –nssize erlaubt eine größere Anzahl von Collections.

Schemalosigkeit

Falls Sie den Einführungsartikel zu NoSQL nicht gelesen haben, hier eine kurze Einführung in dokumentorientierte Datenbanken (DODB) und Schemalosigkeit: Anders als in relationalen Datenbanksystemen (RDBS), die die Daten in Tabellen und Spalten definiert sind, speichert eine dokumentenorientierte Datenbank die Daten eines Datensatzes in einem Feld in einer festgelegten Notation z. B. JSON. Vereinfacht gesagt, besteht ein Datensatz einer DODB aus einem Feld für die ID und einem großen Feld, in dem die Daten kodiert enthalten sind. Dabei ist es nicht wichtig, dass in jedem Dokument die gleiche Art und Anzahl der Daten enthalten ist. Abhängigkeiten zwischen Datensätzen gibt es in den Datenbanken nicht, sie müssen auf Anwendungsseite durch Einträge in den Datenfeldern manuell geschaffen werden.

[ header = Die Shell ]

Die Shell

Wir laufen uns ein wenig warm und spielen mit der MongoDB Shell, die Sie durch Aufruf von mongo.exe starten. MongoDB erzeugt Datenbanken und Collections, sofern sie nicht vorhanden sind, nach dem Einfügen des ersten Dokuments. Im Folgenden werden wir einige Operationen ausführen, die ich kommentieren werde:

  • >show dbs zeigt alle vorhandenen Datenbanken
  • >use phpmag schaltet um auf Datenbank phpmag (noch nicht vorhanden, auch nicht nach diesem Befehl), die aktuelle Datenbank wird nun mit „db“ angesprochen
  • >db.produkte.insert({„typ“:“Ei“,“gueteklasse“:“A“,“haltung“:“Freilandhaltung“}) erzeugt jetzt die Datenbank „phpmag“, die Collection „produkte“ und ein Dokument mit den Feldern. Die ID wird automatisch erzeugt.
  • >db.produkte.insert({„typ“:“Toilettenpapier“,“lagen“:3}): Ein weiteres Produkt einfügen. Hier brauchen wir weder Güteklasse noch Haltungsart
  • >db.produkte.find()zeigt alle Dokumente der Collection „produkte“ an. Neu hinzugekommen ist das Feld „_id“ vom Typ „ObjectId“
  • >db.produkte.find().count(): Operationen können mit der Punktnotation hintereinander ausgeführt werden. Wir werden noch sehen, dass man mehr als nur zwei Operationen hintereinander benutzen kann.
  • >db.produkte.find({„typ“:“Ei“}): Innerhalb der find-Funktion haben wir jetzt einen Filter gesetzt. Diese Filter begegnen uns auch bei Operationen wie update.
  • >db.produkte.update({„typ“:“Ei“},{„typ“:“grosses Ei“,“gueteklasse“:“A“,“haltung“:“Freilandhaltung“}): Mit update werden bestehende Dokumente verändert. In der einfachsten Version wird das Dokument, das wir mit „typ“:“Ei“ suchen, durch den Inhalt {„typ“:“grosses Ei“,“gueteklasse“:“A“,“haltung“:“Freilandhaltung“} ersetzt. Im Gegensatz zu CouchDB können aber auch einzelne Felder eines Dokuments verändert bzw. hinzugefügt werden:
  • >db.produkte.update({„typ“:“grosses Ei“},{$set:{„preis“:0.90}}): Mit dem Operator $set wird definiert, dass in dem Dokument vom Typ „grosses Ei“ das Element „preis“ auf 0.90 gesetzt wird. Ist dieses Element nicht vorhanden, wird es erzeugt.
  • >db.produkte.update({„preis“:{$exists:false}},{$set:{„preis“:1.20}},true,true): Was passiert hier? Wir suchen ein Produkt, bei dem das Feld „preis“ nicht existiert, setzten dort den Preis auf 1.20, der dritte Parameter bestimmt, dass das Datenfeld angelegt werden soll, wenn es nicht existiert , und der vierte Parameter legt fest, dass alle Dokumente untersucht und verändert werden sollen (per Default: nur das erste gefundene Dokument).
  • >db.produkte.update({},{$set:{„anzahl“:1}},true,true): So langsam erschließt sich einem die Logik: Wir wollen diesmal alle Produkte (daher eine leere Bedingung) um das Feld „anzahl“ mit dem Wert 1 erweitern,
  • >db.produkte.update({„typ“:“grosses Ei“},{$inc:{„anzahl“:-1}},true,true): Die Stärke von MongoDB liegt auch in den Operationen, die auf Datensätze ausgeführt werden können. Hier verändern wir einen Datenwert, ohne ihn zuerst auslesen zu müssen. Da es nur ein $inc gibt, müssen wir zum verringern des Bestandes den Wert von „anzahl“ um -1 erhöhen. Soweit der erste Kontakt mit MongoDB. Natürlich wollen wir nun endlich MongoDB mit PHP nutzen.

RockMongo

Nun ist die Shell nicht gerade eine besonders leichte Art, die Daten in der Datenbank anzuzeigen. Falls Sie gerne mit phpMyAdmin arbeiten, habe ich eine Empfehlung: RockMongo ist eine an phpMyAdmin angelehnte Oberfläche für MongoDB. Laden Sie die letzte Revision herunter und kopieren das Verzeichnis „rockmongo“ auf Ihren Webserver. Ein kurzer Blick in die config.php in dem Verzeichnis zeigt das hinterlegte Standard-Log-in („admin/admin“) an. Damit können Sie sich einloggen und haben eine nahezu vertraute Umgebung für den Umgang mit MongoDB.

Abb. 2: RockMongo

MongoDB und PHP

MongoDB-Dokumente werden in PHP als assoziative Arrays dargestellt. Hierbei bestimmen die Schlüssel des Arrays die einzelnen Dokumentenelemente. Zum Einfügen von Daten in die Datenbank können auch Objekte genutzt werden, hier sind die Attribute die Namen der Felder. Es gibt syntaktisch keine Unterschiede, ob sich beim $produkt in $db->produkte->insert($produkt) um ein Array oder ein Objekt handelt. Allerdings liefert der Treiber alle Dokumente als assoziative Arrays zurück, sodass ein einheitliches Vorgehen anzustreben ist. Achten Sie auch darauf, dass Texte unbedingt in UTF-8 gespeichert werden müssen. Ein einfaches „Müller“ ohne ein utf8_decode wird nicht in der Datenbank gespeichert. Wie Sie vielleicht aus den letzten Artikeln wissen, bin ich ein Freund von Autohäusern. Da liegt es nahe, auch in MongoDB die Autohäuser zu verwalten.

Verbindung zur Datenbank und Auswahl der Collection

Die Verbindung zu MongoDB erfolgt durch Instanziierung der MongoDB-Klasse. Dabei kann als Parameter die Adresse der MongoDB festgelegt werden, sofern sie sich nicht auf dem gleichen System wie das PHP-Skript befindet.

$mongo=new Mongo(); //setzt eine lokale MongoDB Instanz voraus
$mongo->selectDB('phpmag');
$autohausColl=$mongo->selectDB('phpmag')->selectCollection('autohaus');

Oder als Kurzform:

$autohausColl=$mongo->phpmag->autohaus;

Ein Datensatz wird eingefügt:

$result=$autohausColl->insert (array('_id'=>1,'name'=>'Meier','marken'=>array('Audi','VW')));

Hier wird das Feld „_id“ mitgegeben, sodass kein eigener Key erzeugt wird. Wenn der Key schon vorhanden ist, so wird der Datensatz nicht eingefügt, und $result erhält den Wert 111 (Key vorhanden), bzw. 1 (erfolgreich). Um einen doppelten Eintrag abzufangen, genügt also folgender Code:

$options=array("save"=>true);
Try {
$result=$autohausColl->insert (array("_id"=>1,"name"=>"Meier","marken"=>array("Audi","VW")));
}catch(Exception $e) {
 echo $e->getMessage();
}

Es gibt zwei Optionen zu insert: save und fsync. Wird save auf true gesetzt (default:false), dann wartet PHP bis zum Ergebnis der Aktion und wirft einen Fehler, falls die Aktion nicht erfolgreich sein sollte. Die Option fsync stellt, sofern auf true gesetzt (default:false) sicher, dass Änderungen an der Datenbank auch auf den Massenspeicher der Datenbank geschrieben worden sind. Da fsync nur in Verbindung mit save sinnvoll ist, reicht es aus, nur fsync auf true zu setzten, save wird dann automatisch gesetzt.
Der aufmerksame Leser bemerkt es sofort: Ich habe in dem Datensatz die Stadt unterschlagen. Nun, es gibt weitaus schwerere Aufgaben, als in MongoDB einen Datensatz zu ändern. Wie schon in dem Eingangsbeispiel gibt es die Funktion „update()“. MongoDB erlaubt das Update ganzer Dokumente, wie es auch in CouchDB der Fall ist. Um die Adressen hinzuzufügen, kann also das Array um das Adressenfeld erweitert werden, und dann per update($neuesArray) eingespielt werden. Doch MongoDB erlaubt auch Operationen auf Elementebene. Mit „$set“ und „$unset“ können Felder erzeugt und verändert sowie komplett  gelöscht werden. Diese Methoden können auf alle Dokumente in einer Collection oder durch Setzen von Filtern auch auf einzelne Dokumente durchgeführt werden. Gehen wir davon aus, dass es außer dem Autohaus Meier noch weitere Autohäuser mit diesen Daten in der Collection gibt. Im ersten Beispiel fügen wir die Adresse mit leeren Angaben in jedes Dokument ein (zugegeben, ein ziemlich blödes Beispiel, da gerade die Schemalosigkeit ein Argument gegen leere Felder ist. Was leer ist, wird einfach weggelassen. Doch nehmen wir an, das Frontend braucht aus irgendeinem Grund die leeren Angaben, vielleicht zum Generieren von Eingabemasken).

$options=array("multiple"=>true); // Alle Datensätze ändern. Default: Nur einer
$filter=array(); // keine Filter gesetzt, alle Datensätze betroffen
$update=array('$set'=>array("Adresse"=>array("plz"=>null,"Stadt"=>null)));
$autohausColl ->update($filter,$update,$options);

Nun haben alle Autohäuser das neue Element „Adresse“. Um ein ganz spezielles Autohaus zu füllen, müssen ein oder mehrere Kriterien Filterkriterien gesetzt werden. Für unser Autohaus Meier sieht es also so aus:

$filter=array("name"=>"Meier"); // Alternativ auch: "_id"=>1
$update=array('$set'=>array("Adresse"=>array("plz"=>13507,"Stadt"=>"Berlin")));

Der Update-Befehl ist ein recht mächtiges Werkzeug, um Daten zu ändern. So können nicht nur einzelne Werte neu gesetzt werden, sondern Operationen auf Arrays (Werte hinzufügen/löschen) , und auf Zahlenwerte (erhöhen/verringern) ausgeführt werden, ohne die Daten vorab zu laden. Aber auch die Filterfunktionen haben einiges zu bieten. So kann nicht nur nach einem oder mehreren Feldern gefiltert werden, auch Vergleichsoperationen wie >,<,>=,<=,!= sind möglich, ebenso wie die Abfrage, ob Felder von einem bestimmten Typ sind. Sogar JavaScript ist in den Filtern erlaubt. Es ist die gleiche Filterfunktion, die auch für die Selektion der Daten benutzt wird.

Anders als in der Shell, wo die Funktion find() alle Daten geliefert hat, bekommt man im PHP-Treiber ein MongoCursorObject zurück. Dieses Objekt kann mit getNext() durchgewandert werden, und liefert das aktuelle Dokument als assoziatives Array:

$cursor=$autohausColl->find();
while($doc=$cursor->getNext()) {
  foreach($doc as $key=>$value) {
    echo $key.":".$value."<br>";
  }
echo "<hr>";
}

Um nur die Autohäuser der Hauptstadt anzuzeigen, wird der find()-Funktion wieder ein Filter mitgegeben. Da die Stadt in diesem Fall ein Teil eines Arrays ist, greifen wir auf dieses Element mit der Punktnotation zu:

$filter=array("adresse.stadt"=>"Berlin");
$cursor=$autohausColl->find($filter);

Wenn mehr als eine Bedingung gewünscht ist, können die Filter hintereinander in das Array geschrieben werden. So sucht $filter=array(‚adresse.stadt’=>’Berlin‘, ‚markenA’=>array(‚$size’=>1)) nach allen Autohäusern in Berlin, die mehr als eine Marke vertreten. Weitere Funktionen, die auf den Cursor angewandt werden können, sind skip, limit, und sort. Ein $cursor=$autohausColl ->find()->skip(2)->limit(5)->sort(array(‚adresse.plz’=>1)) liefert dann die Autohäuser Nr. 3–7 in aufsteigender Reihenfolge der Postleitzahl.

Filter in MongoDB

(’name’=>’Meier‘) Name=’Meier‘ (‚plz’=>array(‚$gt’=>20000)) plz>20000 (‚markenA’=>array(‚$in’=>array(‚VW‘))) „VW“ in markenA (‚markenA’=>array(‚$size’=>1)) nur eine Marke

[ header = Group ]

Group

Ein weiteres wichtiges Feature ist das Gruppieren von Ergebnissen anhand von Feldern. Was in MySQL ein group by ist, bedeutet in MongoDB ein wenig mehr Arbeit, ist aber auch leistungsfähiger. Die group()-Funktion von MongoDB besteht aus drei Parametern:

keys ist ein Array von Feldnamen, anhand derer die Gruppierung stattfinden soll, initial ist der Startwert der Aggregation, und reduce ist die Funktion zum Aggregieren der Werte:

$keys=array('adresse.stadt'=>true);
$initial=array('anzahl'=>0);
$reduceF='function(obj,prev) {prev.anzahl+=1}';
$ret=$collection->group($keys,$initial,$reduceF);

Der Return-Wert $ret liefert ein Array mit mehreren Keys. Mit $ret[‚retval‘] bekommen wir das Ergebnis der Gruppierung als Array mit den Keys, die wir in $keys definiert haben, und den Werten von $initial pro Gruppierung. Nehmen wir an, wir haben zwei Autohäuser in Berlin und drei außerhalb, dann würde das Ergebnis so aussehen:

'adresse.stadt'=>'Berlin', 'anzahl'=>2
'adresse.stadt'=>'Hannover', 'amzahl'=>1
'adresse.stadt'=>'Muenchen', 'anzahl '=>2

Demnach sieht das Ergebnis dann so aus: ‚adresse.stadt’=>’Berlin‘,’anzahl’=>2,’items’=>array(„Meier“,“Mueller“) usw. Die Anzahl der ausgegebenen Keys ist allerdings auf 10 000 begrenzt. Wenn mehr als diese Anzahl benötigt wird, dann muss mit mapreduce() gearbeitet werden.

Map/reduce

Map/reduce ist im Artikel zu CouchDB bereits beschrieben worden, daher hier nur eine kurze Erläuterung. Der Vorgang teilt sich in zwei Phasen auf. Während der map-Phase werden anhand der Funktion Key-Value-Paare gebildet. In der reduce-Phase werden diese Paare aggregiert. Wie CouchDB nutzt auch MongoDB JavaScript als Sprache für die map/reduce-Funktionen. Map/reduce-Aufrufe werden als Kommando direkt an die Datenbank geschickt, das Ergebnis $ret enthält unter $ret[„result“] eine Referenz auf eine temporäre Collection, die mit der Funktion selectCollection() angesprochen und auf die die Funktion find() angewandt wird:

 $map= 'function() {emit(this.adresse.stadt,1);}';
$reduce= 'function(key,value) {var anzahl=0;for(index in value) {anzahl+=value[index];} return anzahl;}';
$db=$mongo->phpmag;
$ret=$db->command(array("mapreduce"=>"autohaus",
							"map"=>$map,
							"reduce"=>$reduce,
							));
$resultA=$db->selectCollection($ret["result"])->find();
foreach($resultA as $result) {
	print_r($result);
}

Referenzen zwischen Dokumenten

Eine der Eigenschaften von NoSQL ist auch der Verzicht von JOINS. Alle Daten sollten in einem Dokument gehalten werden, das Prinzip der Foreign Keys aus relationalen Datenbanken soll hier nicht gelten. MongoDB erlaubt allerdings die Referenzierung von Dokumenten untereinander, das Zauberwort heißt DBRef. Das Verfahren dazu ist recht einfach. In einem Dokument, das eine Referenz auf ein anderes Dokument beinhalten soll, bekommt ein Feld einen Array mit den Keys $ref und $id. Der Wert in $ref ist der Name der Collection, und der Wert in $id ist die ID des Dokuments, auf das verwiesen wird. Dieses Objekt kann manuell eingetragen werden, es gibt aber auch eine Funktion dafür: $ahref_1=MongoDBRef::create(„autohaus“,1). Hier wird eine Referenz auf das Dokument mit der _id=1 aus der Collection „autohaus“ erzeugt. Legen wir nun einen Kunden mit $mongo->phpmag->autohaus->insert(array(„_id“=>1,“name“=>“Kurowski“,“autohaus“=>$ahref_1)) an, dann haben wir folgenden Eintrag erzeugt: {„id“=>1,“name“=>“Kurowski“,“autohaus“=>array(„$ref“=>“autohaus“,“$id“=>1)}.

Angenommen, wir bekommen dieses Dokument als Ergebnis einer Abfrage ($ergebnisDoc), und möchten nun auch noch das Autohaus wissen. Das könnten wir manuell mit einer Abfrage aus dem Ergebnis von $ref und $id bekommen, einfacher aber geht es mit den Bordmitteln: $autohaus=MongoDBRef::get($mongo->phpmag,$ergebnisDoc[„autohaus”]);. Mehr als eine Vereinfachung auf Clientseite ist diese Referenz allerdings nicht, ein echtes JOIN bzw. ein Filter, der auf die Elemente eines referenzierten Dokuments zugreifen soll, ist damit nicht möglich.

[ header = Dateien ]

Dateien

MongoDB nutzt für die Datenspeicherung das GridFS Filesystem. Dieses Filesystem speichert große Dateien in einer Collection von vielen kleinen Teilen (Chunks) und alle Metadaten dieser Datei in einer gesonderten Datei. Um eine Datei zu speichern, wird die GridFS-Klasse benutzt:

 $mongo=new Mongo();
$db=$mongo->phpmag;
$bild=file_get_contents('DSC06830.JPG');
$gridfs=$db->getGridFS();
$id=$gridFS->storeBytes($bild,array("metadata”=>array("name”=>”DSC06830.JPG”,”tags”=>array("Urlaub”,”warm”))));

Um alle gespeicherten Dateien aufzulisten, genügt ein einfaches find() :

$cursor=$gridFS->find();

Über diesen Cursor können Sie wieder iterieren und die Eigenschaften folgendermaßen auslesen:

foreach($cursor as $object){
  echo $object->file["_id"]->__toString().": ".$object->file["metadata"]["name"];
}

Natürlich funktioniert auch hier die Filterfunktion für find(), sodass nur die Einträge gezeigt werden, die dem Filter entsprechen. Genauso verfahren wir, um eine Datei zu erhalten. Gehen wir davon aus, dass die ID des Bildes als String vorliegt (z. B. „4cee1307ffa4515410150000“), dann kann das Bild folgendermaßen angezeigt werden:

$db=$mongo->phpmag;
$gridFS=$db->getGridFS();
$id=new MongoId('4cee1307ffa4515410150000');
$file=$gridFS->findOne(array("_id"=>$id));
header('content-type:image/jpeg');
echo $file->getBytes();
exit;

Dateien, die nicht mehr gebraucht werden, können mit $gridFS->delete($id) gelöscht werden.

Weitere Features

Ich habe Ihnen nun die wichtigsten Features für den Einstieg in MongoDB nahegebracht. Doch MongoDB bietet noch mehr als die hier vorgestellten Möglichkeiten. So können zusätzlich zu dem „_id“-Index eigene Indizes erstellt werden. Diese Indizes können aus mehreren Feldern bestehen, aus Feldern in Objekten eines Wertes (‚adresse.stadt‘) und aus der Kombination beider Möglichkeiten. Indizes können wahlweise eindeutig oder mehrfach vorhanden sein. Ein weiteres Highlight bei Indizes ist die Möglichkeit, anhand von geografischen Daten Näherungssuchen durchzuführen.

Capped Collections sind Collections fester Größe. Bei Überschreitung dieser Größe wird das älteste Dokument gelöscht und das neue gespeichert. So können relativ leicht Logfiles etc. umgesetzt werden. Performanceprobleme können entweder durch explain() oder den eingebauten Profiler untersucht werden. Die Funktion db.autohaus.find().explain() liefert verschiedene Informationen zu dieser Abfrage. Der Profiler speichert die Laufzeiten der Abfragen in einer Capped Collection. Zum Einschalten des Profilers genügt ein Aufruf von db.setProfilingLevel(ms), wobei ms die Zeit der Abfragen in Millisekunden ist, ab der das Logging stattfindet. Ausgeschaltet wird der Profiler mit db.setProfilingLevel(0), die Ausgabe erfolgt über db.system.profile.find().

MongoDB bietet sowohl reichhaltige Master-Slave Replikationsmöglichkeiten als auch die Möglichkeit zum Sharding, bei dem große Datenmengen auf mehrere Datenbanken aufgeteilt werden. Anders als beim Replizieren, wo alle Daten auf den Systemen redundant vorhanden sind, werden beim Sharding die Daten aufgeteilt. Sowohl das Sharding als auch die Replikationsmöglichkeiten von MongoDB sind vielfältig, sodass hier auf externe Dokumentationen verwiesen werden muss.

Fazit: MongoDB vs. CouchDB

MongoDB versucht, den Einstieg in die Welt der NoSQL-Datenbanken leicht zu machen, in dem es eine für viele Nutzer verständlichere Art der Datenabfrage als map/reduce bietet. Dadurch ist der Einstieg leichter, und vor allem die Abfragen gelingen anfangs einfacher als bei CouchDB. Das mag auch an der vertrauten Art der Programmierung von Datenbanken mit PHP liegen. Doch obwohl beide Datenbanken oft zusammen genannt werden, unterscheiden sie sich doch in ihrer Philosophie. Während MongoDB ansetzt, die Datenhaltung in der klassischen Drei-Schichten-Anwendung (Frontend-Backend-Datenbank) zu ersetzten, wartet CouchDB mit einem ganz anderen Ansatz auf. Durch die Verwendung von HTTP und REST als Transportmittel und der Möglichkeit der integrierten Programmierung und des HTTP-Servers in CouchDB sind Anwendungen ohne die Backend-Schicht realisierbar. Doch auch das gesamte Potenzial von MongoDB kann erst nach einiger Zeit erschlossen werden. Durch die Verwendung von Javascript in den Abfragen und Filtern sind auch hier viele Möglichkeiten vorhanden, die sich einem erst nach einiger Zeit erschließen. Beide Datenbanksysteme bieten genug „Wow“-Potenzial, doch am Ende bleibt die alte Weisheit: „Choose the right tool for the right job“.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -