In diesem Lernprogramm lernst du die Datenbank MongoDB kennen. Du erfährst, wie du die Software installierst, Daten manipulierst und die Datenentwurfstechniken auf deine eigenen Anwendungen anwendest.

Alle Beispiele wurden mit MongoDB 5 entwickelt, aber die meisten funktionieren auch mit früheren oder neueren Versionen. Der Code kann direkt in eine Client-Anwendung oder in die MongoDB-Shell (mongo oder mongosh) eingegeben werden, um die Datenbank abzufragen und zu aktualisieren.

Was ist MongoDB?

MongoDB ist eine quelloffene NoSQL-Datenbank. NoSQL bedeutet, dass die Datenbank keine relationalen Tabellen wie eine traditionelle SQL-Datenbank verwendet.

Es gibt eine Reihe von NoSQL-Datenbanktypen, aber MongoDB speichert Daten in JavaScript-ähnlichen Objekten, die als Dokumente bezeichnet werden und deren Inhalt wie folgt aussieht:

{
  _id: "123",
  name: "Craig"
}

Obwohl MongoDB zum Synonym für das JavaScript-basierte Framework Node.js geworden ist, gibt es offizielle MongoDB-Datenbanktreiber für die meisten Frameworks, Sprachen und Laufzeiten, darunter Node.js, PHP und Python. Du kannst dich auch für Bibliotheken wie Mongoose entscheiden, die eine höhere Abstraktionsebene oder ORM-Funktionen (Object Relational Mapping) bieten.

Im Gegensatz zu SQL-Tabellen gibt es in MongoDB keine strukturellen Beschränkungen für die Daten, die du speichern kannst. Datenschemata werden nicht erzwungen: Du kannst speichern, was du willst, wo du willst. Das macht MongoDB ideal für organische – oder unordentliche – Datenstrukturen.

Nimm ein Adressbuch für Kontakte. Personen können oft mehrere Telefonnummern haben. Du könntest drei Telefonfelder in einer SQL-Tabelle definieren, aber das wären für manche Kontakte zu viele und für andere zu wenige. Letztendlich benötigst du eine separate Telefontabelle, was die Komplexität noch erhöht.

In MongoDB können diese Telefonnummern als unbegrenztes Array von Objekten im selben Dokument definiert werden:

{
  _id: "123",
  name: "Craig",
  telephone: [
    { home: "0123456789" },
    { work: "9876543210" },
    { cell: "3141592654" }
  ]
}

Beachte, dass MongoDB eine ähnliche JavaScript-Objektnotation für Datenaktualisierungen und -abfragen verwendet, was dich vor einige Herausforderungen stellen kann, wenn du an SQL gewöhnt bist.

Elemente von MongoDB

Bevor wir weitermachen, lass uns einen Blick darauf werfen, was MongoDB ausmacht. Dieses Vokabular werden wir in diesem Artikel verwenden.

  • Dokument: Ein einzelnes Objekt in einem Datenspeicher, vergleichbar mit einem Datensatz oder einer Zeile in einer SQL-Datenbanktabelle.
  • Feld: Ein einzelnes Datenelement innerhalb eines Dokuments, z. B. ein Name oder eine Telefonnummer, analog zu einem SQL-Feld oder einer Tabellenspalte.
  • Sammlung: Ein Satz ähnlicher Dokumente, analog zu einer SQL-Tabelle. Du könntest zwar alle deine Dokumente in einer einzigen Sammlung zusammenfassen, aber es ist meist praktischer, sie nach bestimmten Typen zu gruppieren. In einem Kontaktadressbuch könntest du eine Sammlung für Personen und eine für Unternehmen anlegen.
  • Datenbank: Eine Sammlung von zusammenhängenden Daten, identisch mit einer SQL-Datenbank.
  • Schema: Ein Schema definiert Datenstrukturen. In SQL-Datenbanken musst du Tabellendefinitionen mit zugehörigen Feldern und Typen festlegen, bevor du Daten speichern kannst. In MongoDB ist dies nicht notwendig, obwohl es möglich ist, ein Schema zu erstellen, das Dokumente validiert, bevor sie einer Sammlung hinzugefügt werden können.
  • Index: Eine Datenstruktur, die zur Verbesserung der Abfrageleistung verwendet wird und mit SQL-Indizes identisch ist.
  • Primärschlüssel: Ein eindeutiger Bezeichner für jedes Dokument. MongoDB fügt automatisch ein eindeutiges und indiziertes _id-Feld zu jedem Dokument in einer Sammlung hinzu.
  • Denormalisierung: In SQL-Datenbanken ist die „Normalisierung“ eine Technik, um Daten zu organisieren und Duplikate zu vermeiden. In MongoDB wird die „Denormalisierung“ gefördert. Du wiederholst aktiv Daten und ein einziges Dokument könnte alle benötigten Informationen enthalten.
  • Joins: SQL bietet einen JOIN-Operator, mit dem Daten aus mehreren normalisierten Tabellen in einer einzigen Abfrage abgerufen werden können. Joining war in MongoDB bis Version 3.6 nicht möglich und die Einschränkungen bleiben bestehen. Dies ist ein weiterer Grund, warum Daten in eigenständige Dokumente denormalisiert werden sollten.
  • Transaktionen: Wenn eine Aktualisierung zwei oder mehr Werte in einem einzigen Dokument ändert, stellt MongoDB sicher, dass sie alle erfolgreich sind oder fehlschlagen. Aktualisierungen von zwei oder mehr Dokumenten müssen in einer Transaktion verpackt werden. MongoDB unterstützt Transaktionen seit Version 4.0, aber dafür ist ein Multi-Server-Replikat oder ein Sharded-Cluster erforderlich. Die Beispielinstallationen unten verwenden einen einzelnen Server, so dass Transaktionen nicht möglich sind.

So installierst du MongoDB

Du hast drei Möglichkeiten, MongoDB auf deinem lokalen Rechner zu nutzen. Wir führen dich durch jede davon.

1. Docker verwenden (empfohlen)

Docker ist ein Software-Management-Tool, mit dem du MongoDB oder jede andere Anwendung in wenigen Minuten installieren, konfigurieren und ausführen kannst.

Installiere Docker und Docker Compose und erstelle dann einen Projektordner mit einer einzigen Datei namens docker-compose.yml, die den folgenden Inhalt enthält (beachte, dass die Einrückungen wichtig sind):

version: '3'

services:

  mongodb:
    image: mongo:5
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=pass
      - MONGO_INITDB_DATABASE=mongodemo
    container_name: mongodb
    volumes:
      - dbdata:/data/db
    ports:
      - "27017:27017"

  adminer:
    image: dehy/adminer
    container_name: adminer
    depends_on:
      - mongodb
    ports:
      - "8080:80"

volumes:
  dbdata:

Rufe den Ordner über die Kommandozeile auf und führe ihn aus:

docker-compose up

Die neueste Version von MongoDB 5 wird heruntergeladen und gestartet. Das dauert beim ersten Start ein paar Minuten, aber die folgenden Durchläufe sind deutlich schneller.

Beachte das:

  • Es wird ein MongoDB-Administratorkonto mit der ID „root“ und dem Passwort „pass“ definiert.
  • Die Daten werden zwischen den Neustarts in einem Docker-Volume namens dbdata gespeichert.
  • Der Adminer Datenbank-Client wird ebenfalls bereitgestellt.

Du kannst jeden MongoDB-Datenbankclient verwenden, um dich mit der ID „root“ und dem Passwort „pass“ mit localhost:27017 zu verbinden. Alternativ kannst du auf den Adminer unter http://localhost:8080/ zugreifen und dich mit den folgenden Anmeldedaten anmelden:

  • System: MongoDB (alpha)
  • Server: host.docker.internal
  • Benutzername: root
  • Passwort: pass
Adminer-Anmeldung
Adminer-Anmeldung

Mit Adminer kannst du Sammlungen und Dokumente einsehen. Beachte jedoch, dass Sammlungen als „Tabellen“ bezeichnet werden:

Adminer Sammlungsansicht
Adminer Sammlungsansicht

Um Befehle auszuführen, kannst du die MongoDB Shell (mongosh) oder die alte mongo Befehlszeilenumgebung REPL (Read Eval Print Loop) verwenden.

Greife auf die Bash-Shell des Docker-MongoDB-Containers zu:

docker exec -it mongodb bash

Starte dann die MongoDB-Shell mit deiner ID und deinem Passwort:

mongosh -u root -p pass

(Wenn du es vorziehst, kannst du auch den alten Befehl mongo verwenden)

Du kannst dann MongoDB-Befehle wie die folgenden eingeben:

  • show dbs; – Alle Datenbanken anzeigen
  • use mongodemo; – Eine bestimmte Datenbank verwenden
  • show collections; – Sammlungen in einer Datenbank auflisten
  • db.person.find(); – Alle Dokumente in einer Sammlung auflisten
  • exit; – Beenden/Schließen der Shell

Beende MongoDB, indem du den folgenden Befehl im Projektverzeichnis ausführst:

docker-compose down

2. Einen Cloud-Anbieter verwenden (keine Installation)

Du kannst eine gehostete MongoDB-Instanz verwenden, sodass du nichts lokal installieren musst. Eine Internetverbindung ist unerlässlich und die Geschwindigkeit der Antwort hängt vom Anbieter und deiner Bandbreite ab. Die meisten Anbieter verlangen eine monatliche Gebühr und/oder eine Gebühr für die Nutzung von Megabytes.

Der Hoster stellt dir normalerweise Informationen zur Verfügung, damit du die Datenbank mit der MongoDB-Client-Software aus der Ferne verwalten kannst.

3. MongoDB vor Ort installieren

MongoDB kann auf Linux, Windows oder Mac OS installiert und konfiguriert werden. Es sind zwei Editionen verfügbar:

  1. Eine kommerzielle Enterprise Edition
  2. Eine quelloffene Community Edition (in diesem Tutorial verwendet)

Auf der MongoDB-Installationsseite findest du Anleitungen für verschiedene Betriebssysteme. Generell gilt:

Befolge die Anweisungen sorgfältig, damit deine Installation erfolgreich ist!

Wie du auf deine MongoDB-Datenbank zugreifst

Jetzt, wo deine MongoDB-Datenbank installiert ist, ist es an der Zeit zu lernen, wie du sie verwalten kannst. Wir erklären dir, was du tun musst, um auf deine Datenbank zuzugreifen und mit ihr zu arbeiten.

1. Installiere einen MongoDB-Client

Um Datenbanken zu verwalten, ist eine MongoDB-Client-Anwendung erforderlich. Wenn du eine Cloud- oder lokale Installation verwendest, empfehlen wir dir, die Kommandozeilenanwendung Mongosh MongoDB Shell zu installieren.

Adminer ist ein webbasierter Datenbank-Client, der MongoDB unterstützt, allerdings ist er derzeit auf die Überprüfung von Sammlungen beschränkt. Adminer kann als einzelnes PHP-Skript heruntergeladen werden, ist aber bereits eingerichtet, wenn du die Docker-Installation nutzt oder DevKinsta installiert hast.

Eine GUI-Client-Anwendung bietet eine bessere Schnittstelle zum Aktualisieren und Prüfen von Daten. Es stehen mehrere Optionen zur Verfügung, darunter das kostenlose und plattformübergreifende MongoDB Compass:

MongoDB Compass
MongoDB Compass

Studio 3T, ein weiterer GUI-Anwärter, bietet eine kommerzielle Anwendung mit eingeschränkter Funktionalität zum Nulltarif an:

Studio 3T
Studio 3T

Du kannst mit einem dieser Tools auf deine MongoDB-Datenbank zugreifen, indem du eines der folgenden Tools verwendest:

  1. Den Netzwerknamen, die URL oder die IP-Adresse des Rechners (localhost für eine lokale Installation).
  2. Den MongoDB-Port (standardmäßig 27017).
  3. Eine Benutzer-ID und ein Passwort. Normalerweise wird bei der Installation ein Root-Benutzer festgelegt.

2. Datenbankzugriffsberechtigungen festlegen und speichern

Der Root-Administrator hat uneingeschränkten Zugriff auf alle Datenbanken. In der Regel solltest du einen benutzerdefinierten Benutzer mit bestimmten Berechtigungen verwenden, um den Zugriff einzuschränken und die Sicherheit zu erhöhen.

Der folgende Befehl erstellt zum Beispiel einen Benutzer namens myuser mit dem Passwort mypass, der Lese- und Schreibrechte für die Datenbank mydb hat:

use mydb;

db.createUser({
  user: "myuser",
  pwd: "mypass",
  roles: [
    { role: "readWrite", db: "mydb" }
  ]
});

Wie man neue Dokumente in MongoDB einfügt

Bevor du dein erstes Dokument einfügst, musst du weder eine Datenbank noch eine Sammlung definieren. Wechsle mit einem beliebigen MongoDB-Client einfach zu einer Datenbank namens mongodemo:

use mongodemo;

Dann fügst du ein einzelnes Dokument in eine neue Personensammlung ein:

db.person.insertOne(
  {
    name: 'Abdul',
    company: 'Alpha Inc',
    telephone: [
      { home: '0123456789' },
      { work: '9876543210' }
    ]
  }
);

Zeige das Dokument an, indem du eine Abfrage ausführst, die alle Ergebnisse aus der Personensammlung zurückgibt:

db.person.find({});

Das Ergebnis sieht dann etwa so aus:

{
  "_id" : ObjectId("62442429854636a03f6b8534"),
  name: 'Abdul',
  company: 'Alpha Inc',
  telephone: [
    { home: '0123456789' },
    { work: '9876543210' }
  ]
}

Wie man mehrere Dokumente einfügt

Du kannst mehrere Dokumente in eine Sammlung einfügen, indem du ein Array an insertMany() übergibst. Der folgende Code erstellt zusätzliche Personendokumente und eine neue Unternehmenssammlung:

db.person.insertMany([
  {
    name: 'Brian',
    company: 'Beta Inc'
  },
  {
    name: 'Claire',
    company: 'Gamma Inc',
    telephone: [
      { cell: '3141592654' }
    ]
  },
  {
    name: 'Dawn',
    company: 'Alpha Inc'
  },
  {
    name: 'Esther',
    company: 'Beta Inc',
    telephone: [
      { home: '001122334455' }
    ]
  },
  {
    name: 'George',
    company: 'Gamma Inc'
  },
  {
    name: 'Henry',
    company: 'Alpha Inc',
    telephone: [
      { work: '012301230123' },
      { cell: '161803398875' }
    ]
  },
]);

db.company.insertMany([
  {
    name: 'Alpha Inc',
    base: 'US'
  },
  {
    name: 'Beta Inc',
    base: 'US'
  },
  {
    name: 'Gamma Inc',
    base: 'GB'
  },
]);

Woher kommt die _id?

MongoDB weist jedem Dokument in einer Sammlung automatisch eine _id zu. Dies ist eine ObjectID – ein BSON (Binary Javascript Object Notation) Wert, der Folgendes enthält:

  • Die Unix-Epoche in Sekunden zum Zeitpunkt der Erstellung (4 Byte)
  • Aa 5-Byte-Maschinen-/Prozess-ID
  • Ein 3-Byte-Zähler, der mit einem Zufallswert beginnt

Dies ist der Primärschlüssel des Dokuments. Der 24-stellige hexadezimale Wert ist garantiert eindeutig für alle Dokumente in der Datenbank und kann nach dem Einfügen nicht mehr geändert werden.

MongoDB bietet auch eine getTimeStamp()-Funktion, mit der du das Erstellungsdatum/die Erstellungszeit des Dokuments abfragen kannst, ohne dass du explizit einen Wert festlegen musst. Alternativ kannst du bei der Erstellung eines Dokuments einen eigenen eindeutigen _id-Wert definieren.

Denormalisierung der Daten

Die oben eingefügten Datensätze setzen das Unternehmen jedes Benutzers auf eine Zeichenfolge wie „Alpha Inc“. Dies ist in normalisierten SQL-Datenbanken nicht empfehlenswert:

  • Es ist leicht, einen Fehler zu machen: Ein Benutzer ist der „Alpha Inc.“ zugeordnet, ein anderer der „Alpha Inc.“ (zusätzlicher Punkt). Sie werden als unterschiedliche Unternehmen behandelt.
  • Die Aktualisierung eines Firmennamens könnte bedeuten, dass viele Datensätze aktualisiert werden müssen.

Die SQL-Lösung besteht darin, eine Unternehmenstabelle zu erstellen und ein Unternehmen über seinen Primärschlüssel (wahrscheinlich eine ganze Zahl) mit einer Person zu verknüpfen. Der Schlüssel würde unabhängig von Änderungen des Firmennamens gleich bleiben und die Datenbank kann Regeln zur Gewährleistung der Datenintegrität durchsetzen.

Die Denormalisierung wird in MongoDB gefördert. Du solltest Daten aktiv wiederholen und ein einziges Dokument könnte alle benötigten Informationen enthalten. Das hat mehrere Vorteile:

  • Dokumente sind in sich geschlossen und leichter zu lesen – es ist nicht nötig, auf andere Sammlungen zu verweisen.
  • Die Schreibleistung kann schneller sein als bei einer SQL-Datenbank, da weniger Datenintegritätsregeln durchgesetzt werden müssen.
  • Das Sharding – also die Verteilung von Daten auf mehrere Rechner – wird einfacher, weil es nicht notwendig ist, auf Daten in anderen Collections zu verweisen.

Einfache MongoDB-Abfragen

Du kannst alle Dokumente in einer Sammlung, z. B. einer Person, auflisten, indem du ein leeres find() verwendest:

db.person.find({})

Die Methode count() gibt die Anzahl der Dokumente zurück (in unserem Fall sind es 7):

db.person.find({}).count();

Eine sort()-Methode gibt die Dokumente in einer beliebigen Reihenfolge zurück, z. B. nach Namen in umgekehrter alphabetischer Reihenfolge:

db.person.find({}).sort({ name: -1 });

Du kannst auch die Anzahl der zurückgegebenen Dokumente begrenzen, z.B. die ersten drei Namen finden:

db.person.find({}).sort({ name: 1 }).limit(2);

Du kannst nach bestimmten Datensätzen suchen, indem du eine Abfrage mit einem oder mehreren Feldern definierst, z. B. alle Personendokumente finden, in denen der Name „Claire“ steht:

db.person.find({ name: 'Claire' });

Logische Operatoren wie $and, $or, $not, $gt (größer als), $lt (kleiner als) und $ne (ungleich) werden ebenfalls unterstützt, z. B. alle Personendokumente, bei denen das Unternehmen entweder „Alpha Inc“ oder „Beta Inc“ ist:

db.person.find({
  $or: [
    { company: 'Alpha Inc' },
    { company: 'Beta Inc' }
  ]
});

In der Beispieldatenbank könnte das gleiche Ergebnis mit $nin (nicht in) erzielt werden, um alle Dokumente zu extrahieren, bei denen das Unternehmen nicht „Gamma Inc“ ist:

db.person.find({
  company: { $nin: ['Gamma Inc'] }
});

Ein zweites Wertobjekt in der find()-Methode legt eine Projektion fest, die die zurückgegebenen Felder definiert. In diesem Beispiel wird nur der Name zurückgegeben (die _id des Dokuments wird immer zurückgegeben, es sei denn, sie wird explizit ausgeschaltet):

db.person.find(
  { name:'Claire' },
  { _id:0, name:1 }
);

Das Ergebnis:

{
  "name" : "Claire"
}

Mit der Abfrage $elemMatch kannst du Elemente in einem Array finden, z. B. alle Dokumente, in denen das Telefon-Array ein Arbeitselement enthält. Das gleiche $elemMatch kann in der Projektion verwendet werden, um nur die Arbeitsnummer anzuzeigen:

db.person.find(
  {
    telephone: { $elemMatch: { work: { $exists: true }} }
  },
  {
    _id: 0,
    name:1,
    telephone: { $elemMatch: { work: { $exists: true }}}
  }
);

Das Ergebnis:

{
  "name" : "Abdul",
  "telephone" : [
    { "work" : "9876543210" }
  ]
},
{
  "name" : "Henry",
  "telephone" : [
    { "work" : "012301230123" }
  ]
}

Cursors in MongoDB verwenden

Die meisten Datenbanktreiber erlauben es, die Ergebnisse einer Abfrage als Array oder ähnliche Datenstruktur zurückzugeben. Wenn diese Menge jedoch Tausende von Dokumenten enthält, kann das zu Speicherproblemen führen.

Wie die meisten SQL-Datenbanken unterstützt auch MongoDB das Konzept der Cursor. Mit Cursors kann eine Anwendung die Abfrageergebnisse einzeln lesen, bevor sie mit dem nächsten Element fortfährt oder die Suche abbricht.

Cursors können auch von einer MongoDB-Shell aus verwendet werden:

let myCursor = db.person.find( {} );

while ( myCursor.hasNext() ) {
  print(tojson( myCursor.next() ));
}

Wie man Indizes in MongoDB erstellt

Die Personensammlung enthält derzeit sieben Dokumente, so dass eine Abfrage nicht sehr rechenintensiv ist. Stell dir aber vor, du hast eine Million Kontakte mit einem Namen und einer E-Mail-Adresse. Die Kontakte können nach Namen geordnet sein, aber die E-Mail-Adressen werden in einer scheinbar zufälligen Reihenfolge stehen.

Wenn du einen Kontakt nach seiner E-Mail-Adresse suchen willst, müsste die Datenbank bis zu einer Million Einträge durchsuchen, bevor sie eine Übereinstimmung findet. Durch das Hinzufügen eines Indexes für das E-Mail-Feld wird eine Nachschlagetabelle erstellt, in der die E-Mails in alphabetischer Reihenfolge gespeichert werden. Die Datenbank kann nun effizientere Suchalgorithmen verwenden, um die richtige Person zu finden.

Indizes werden wichtig, wenn die Anzahl der Dokumente steigt. Im Allgemeinen solltest du einen Index auf jedes Feld anwenden, auf das in einer Abfrage Bezug genommen werden könnte. Du könntest Indizes auf jedes Feld anwenden, aber sei dir bewusst, dass dies die Datenaktualisierung verlangsamen und den benötigten Speicherplatz erhöhen würde, da eine erneute Indizierung erforderlich wird.

MongoDB bietet eine Reihe von Indexarten.

Einzelfeld-Indizes

Die meisten Indizes werden auf einzelne Felder angewandt, z.B. das Namensfeld in aufsteigender alphabetischer Reihenfolge indizieren:

db.person.createIndex({ name: 1 });

Die Verwendung von -1 kehrt die Reihenfolge um. Das wäre in unserem Beispiel von geringem Nutzen, aber es könnte praktisch sein, wenn du ein Datumsfeld hast, in dem neuere Ereignisse Vorrang haben.

Drei weitere Indizes sind in der Beispiel-Datenbank mongodemo nützlich:

db.person.createIndex( { company: 1 } );
db.company.createIndex( { name: 1 } );
db.company.createIndex( { base: 1 } );

Zusammengesetzte Indizes für mehrere Felder

Zwei oder mehr Felder können in einem Index angegeben werden, z. B.

db.person.createIndex( { name: 1, company: 1 } );

Dies kann nützlich sein, wenn ein Feld regelmäßig in Verbindung mit einem anderen in Suchanfragen verwendet wird.

Multikey-Indizes auf Array- oder Objektelementen

Dokumente können sehr komplex sein und es ist oft notwendig, Felder tiefer in der Struktur zu indizieren, wie z. B. die Telefonnummer der Arbeit:

db.products.createIndex( { 'telephone.work': 1 } );

Wildcard-Indizes

Ein Wildcard-Index kann jedes Feld in einem Dokument indizieren. Das ist in der Regel bei kleineren und einfacheren Dokumenten praktisch, die auf verschiedene Arten abgefragt werden können:

db.company.createIndex( { '$**': 1 } );

Volltext-Indizes

Mit einem Textindex kannst du suchmaschinenähnliche Abfragen erstellen, die den Text in allen Stringfeldern untersuchen und nach Relevanz ordnen. Du kannst den Textindex auf bestimmte Felder beschränken:

db.person.createIndex( { name: "text", company: "text" } );

…oder einen Textindex für alle String-Felder erstellen:

db.person.createIndex( { "$**": "text" } );

Mit dem $text-Operator kannst du diesen Index durchsuchen, zum Beispiel alle Dokumente finden, in denen „Gamma“ erwähnt wird:

db.person.find({ $text: { $search: 'Gamma' } });

Beachte, dass Volltextsuchen in der Regel fünf oder mehr Zeichen benötigen, um brauchbare Ergebnisse zu liefern.

Andere Index-Typen

MongoDB bietet mehrere andere spezialisierte Index-Typen:

Wie man MongoDB-Indizes verwaltet

Die für eine Sammlung definierten Indizes können mit untersucht werden:

db.person.getIndexes();

Dies gibt ein Array von Ergebnissen zurück, wie zum Beispiel:

[
  {
    "v" : 2.0,
    "key" : { "_id" : 1.0 },
    "name" : "_id_"
  },
  {
    "v" : 2.0,
    "key" : { "company" : 1.0 },
    "name" : "company_1"
  },
  {
    "v" : 2.0,
    "key" : { "name" : 1.0 },
    "name" : "name_1"
  }
]

Der „Schlüssel“ definiert das Feld und die Reihenfolge, während „Name“ ein eindeutiger Bezeichner für den Index ist – z. B. „Firma_1“ für den Index des Feldes Firma.

Die Effektivität eines Indexes kann untersucht werden, indem eine .explain()-Methode zu jeder Abfrage hinzugefügt wird, z. B.

db.person.find({ name:'Claire' }).explain();

Dies gibt einen großen Datensatz zurück, aber das Objekt „winningPlan“ zeigt den in der Abfrage verwendeten „indexName“:

"winningPlan" : {
  "stage" : "FETCH",
  "inputStage" : {
    "stage" : "IXSCAN",
    "keyPattern" : { "name" : 1.0 },
    "indexName" : "name_1",
  }
}

Falls nötig, kannst du einen Index löschen, indem du auf seinen Namen verweist:

db.person.dropIndex( 'name_1' );

oder mit Hilfe des Index-Spezifikationsdokuments:

db.person.dropIndex({ name: 1 });

Mit der dropIndexes()-Methode kannst du mehr als einen Index in einem einzigen Befehl löschen.

MongoDB-Datenvalidierungsschemata verwenden

Im Gegensatz zu SQL sind in MongoDB keine Datendefinitionsschemata erforderlich. Du kannst jederzeit beliebige Daten in ein beliebiges Dokument in einer beliebigen Sammlung stellen.

Das gibt dir viel Freiheit. Es kann jedoch vorkommen, dass du darauf bestehen willst, dass Regeln eingehalten werden. Zum Beispiel sollte es nicht möglich sein, ein Dokument in die Personensammlung einzufügen, wenn es keinen Namen enthält.

Validierungsregeln können mit Hilfe eines $jsonSchema-Objekts festgelegt werden, das ein Array von erforderlichen Elementen und die Eigenschaften jedes validierten Feldes definiert. Die Personensammlung wurde bereits erstellt, aber du kannst trotzdem ein Schema definieren, das festlegt, dass ein Namensstring erforderlich ist:

db.runCommand({
  collMod: 'person',
  validator: {
    $jsonSchema: {
      required: [ 'name' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
        }
      }
    }
  }
});

Versuche, ein Personendokument ohne einen Namen einzufügen:

db.person.insertOne({ company: 'Alpha Inc' });

…und der Befehl wird fehlschlagen:

{
  "index" : 0.0,
  "code" : 121.0,
  "errmsg" : "Document failed validation",
  "op" : {
      "_id" : ObjectId("624591771658cd08f8290401"),
      "company" : "Alpha Inc"
  }
}

Schemas können auch definiert werden, wenn du eine Sammlung erstellst, bevor sie verwendet wird. Der folgende Befehl setzt die gleichen Regeln wie oben um:

db.createCollection('person', {
  validator: {
    $jsonSchema: {
        required: [ 'name' ],
        properties: {
          name: {
          bsonType: 'string',
          description: 'name string required'
          }
      }
    }
  }
});

In diesem komplexeren Beispiel wird eine Benutzersammlung erstellt, die überprüft, dass ein Name, eine E-Mail-Adresse und mindestens eine Telefonnummer angegeben werden müssen:

db.createCollection('users', {
  validator: {
    $jsonSchema: {
      required: [ 'name', 'email', 'telephone' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
          },
          email: {
        bsonType: 'string',
          pattern: '^.+@.+$',
          description: 'valid email required'
          },
        telephone: {
          bsonType: 'array',
          minItems: 1,
          description: 'at least one telephone number required'
          }
      }
    }
  }
});

Wie man bestehende Dokumente in MongoDB aktualisiert

MongoDB bietet mehrere Aktualisierungsmethoden, darunter updateOne(), updateMany() und replaceOne(). Diese werden übergeben:

  • Ein Filterobjekt, das die zu aktualisierenden Dokumente ausfindig macht
  • Ein Update-Objekt – oder ein Array von Update-Objekten – das die zu ändernden Daten beschreibt
  • Ein optionales Options-Objekt. Die nützlichste Eigenschaft ist upsert, mit der ein neues Dokument eingefügt werden kann, wenn keines gefunden wird.

Das folgende Beispiel aktualisiert das Personendokument, in dem der Name auf „Henry“ gesetzt ist. Es entfernt die Telefonnummer für die Arbeit, fügt eine Telefonnummer für zu Hause hinzu und setzt ein neues Geburtsdatum:

db.person.updateOne(
  { name: 'Henry' },
  [
    { $unset: [ 'telephone.work' ] },
    { $set: {
      'birthdate': new ISODate('1980-01-01'),
      'telephone': [ { 'home': '789789789' } ]
    } }
  ]
);

Das nächste Beispiel aktualisiert das Personendokument, in dem der Name „Ian“ steht. Dieser Name existiert derzeit nicht, aber wenn du hochladen auf „true“ setzt, wird er erstellt:

db.person.updateOne(
  { name: 'Ian' },
  { $set: { company: 'Beta Inc' } },
  { upsert: true }
);

Du kannst jederzeit Abfragebefehle ausführen, um die Datenaktualisierungen zu überprüfen.

Wie man Dokumente in MongoDB löscht

Im obigen Beispiel für die Aktualisierung wurde mit $unset die Telefonnummer der Arbeit aus dem Dokument mit dem Namen „Henry“ entfernt. Um ein ganzes Dokument zu löschen, kannst du eine von mehreren Löschmethoden verwenden, z. B. deleteOne(), deleteMany() und remove() (die ein oder mehrere Dokumente löschen können).

Das neu erstellte Dokument für Ian kann mit einem entsprechenden Filter gelöscht werden:

db.person.deleteOne({ name: 'Ian' });

Verwendung von Aggregationsoperationen in MongoDB

Aggregation ist mächtig, aber manchmal schwer zu verstehen. Sie definiert eine Reihe – oder Pipeline – von Operationen in einem Array. Jede Stufe dieser Pipeline führt eine Operation durch, wie z. B. das Filtern, Gruppieren, Berechnen oder Ändern einer Gruppe von Dokumenten. Eine Stufe kann auch ein SQL JOIN-ähnliches Verhalten mit einer $lookup-Operation verwenden. Die daraus resultierenden Dokumente werden an die nächste Stufe der Pipeline weitergeleitet, damit sie bei Bedarf weiterverarbeitet werden können.

Die Aggregation lässt sich am besten anhand eines Beispiels veranschaulichen. Wir werden Schritt für Schritt eine Abfrage erstellen, die den Namen, das Unternehmen und die Telefonnummer (falls verfügbar) aller Personen liefert, die für eine Organisation in den USA arbeiten.

In der ersten Operation wird ein $match ausgeführt, um Unternehmen mit Sitz in den USA zu filtern:

db.company.aggregate([
  { $match: { base: 'US' } }
]);

Dies gibt zurück:

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US"
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US"
}

Wir können dann einen neuen Pipeline-Operator $lookup hinzufügen, der den Unternehmensnamen (localField) mit dem Unternehmen (foreignField) in der Personensammlung (from) abgleicht. Die Ausgabe wird als Mitarbeiter-Array an das Dokument jedes Unternehmens angehängt:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: {
      from: 'person',
      localField: 'name',
      foreignField: 'company',
            as: 'employee'
          }
        }
]);

Und hier ist das Ergebnis:

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8534"),
      "name" : "Abdul",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "0123456789" },
        { "work" : "9876543210" }
      ]
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8537"),
      "name" : "Dawn",
      "company" : "Alpha Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b853a"),
      "name" : "Henry",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "789789789" }
      ],
    }
  ]
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8535"),
      "name" : "Brian",
      "company" : "Beta Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8538"),
      "name" : "Esther",
      "company" : "Beta Inc",
      "telephone" : [
       { "home" : "001122334455" }
      ]
    }
  ]
}

Eine $project-Operation (Projektion) kann nun alle Mitarbeiter-Arrays außer denjenigen der Mitarbeiter entfernen. Danach folgt eine $unwind-Operation, um das Array zu zerstören und separate Mitarbeiterdokumente zu erhalten:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' }
]);

Das Ergebnis:

{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8534"),
    "name" : "Abdul",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "0123456789" },
      { "work" : "9876543210" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8537"),
    "name" : "Dawn",
    "company" : "Alpha Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b853a"),
    "name" : "Henry",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "789789789" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8535"),
    "name" : "Brian",
    "company" : "Beta Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8538"),
    "name" : "Esther",
    "company" : "Beta Inc",
    "telephone" : [
      { "home" : "001122334455" }
    ]
  }
}

Schließlich wird eine $replaceRoot-Operation verwendet, um jedes Dokument so zu formatieren, dass nur der Name, das Unternehmen und die Telefonnummer der Person zurückgegeben werden. Danach folgt eine $sort-Operation, um die Dokumente in aufsteigender Reihenfolge der Namen auszugeben. Die vollständige Aggregatabfrage:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' },
  { $replaceRoot: {
    newRoot: {
      $mergeObjects: [ {
        name: "$employee.name",
        company: '$employee.company',
        work: { $first: '$employee.telephone.work' }
      }, "$name" ]
   } } },
  { $sort: { name: 1 } }
]);

Das Ergebnis:

{
  "name" : "Abdul",
  "company" : "Alpha Inc",
  "work" : "9876543210"
}
{
  "name" : "Brian",
  "company" : "Beta Inc",
}
{
  "name" : "Dawn",
  "company" : "Alpha Inc",
}
{
  "name" : "Esther",
  "company" : "Beta Inc"
}
{
  "name" : "Henry",
  "company" : "Alpha Inc"
}

Es gibt auch andere Möglichkeiten, dieses Ergebnis zu erreichen, aber der wichtigste Punkt ist, dass MongoDB den Großteil der Arbeit erledigen kann. Es ist nur selten nötig, Dokumente zu lesen und die Daten direkt in deinem Anwendungscode zu manipulieren.

So führst du MongoDB-Massenoperationen aus

Standardmäßig kann MongoDB 1.000 gleichzeitige Operationen verarbeiten. Bei der Verwendung von Mongosh ist das wahrscheinlich kein Problem, aber Anwendungen können diese Grenze erreichen, wenn sie eine Reihe von Datenmanipulationen an einzelnen Datensätzen vornehmen. Node.js-Anwendungen sind besonders problematisch, weil sie schnell eine Reihe von asynchronen Anfragen stellen können, ohne warten zu müssen, bis diese abgeschlossen sind.

Um dieses Problem zu umgehen, bietet MongoDB eine API für Massenoperationen, die eine beliebige Anzahl von Aktualisierungen akzeptiert, die nacheinander oder in beliebiger Reihenfolge ausgeführt werden können.

Hier ist ein Pseudocode-Beispiel in Node.js:

// reference the mycollection collection
const bulk = db.collection('mycollection').initializeUnorderedBulkOp();

// make any number of data changes
bulk.insertOne(...);
bulk.insertMany(...)
bulk.updateOne(...);
bulk.deleteOne(...);
// etc...

bulk.execute();

Die letzte Anweisung gibt effektiv eine einzige MongoDB-Anfrage aus, so dass die Wahrscheinlichkeit, das Limit von 1.000 Operationen zu erreichen, geringer ist.

Zusammenfassung

MongoDB bietet einen flexiblen Speicher für Anwendungen wie Content Management Systeme, Adressbücher und soziale Netzwerke, bei denen strenge Datenstrukturen zu starr und schwer zu definieren sind. Daten können schnell geschrieben werden und das Sharding über mehrere Server wird einfacher.

Das Schreiben von Anwendungen mit einer MongoDB-Datenbank kann auch befreiend sein. Es ist möglich, beliebige Daten in jedem Dokument in jeder Sammlung zu speichern. Das ist besonders praktisch, wenn du einen Prototyp oder ein Minimum Viable Product mit agilen Methoden entwickelst, bei denen sich die Anforderungen im Laufe der Zeit ändern.

Allerdings können komplexe Abfragen eine Herausforderung sein und Denormalisierungskonzepte sind schwer zu akzeptieren, wenn du aus der SQL-Welt umsteigst.

MongoDB eignet sich weniger für Anwendungen mit strengen Transaktionsanforderungen, bei denen die Datenintegrität von entscheidender Bedeutung ist, wie z. B. bei Bank-, Buchhaltungs- und Lagerverwaltungssystemen. Hier gibt es identifizierbare Datenfelder, die vor Beginn der Programmierung entworfen werden sollten.

Es gibt viele Anwendungen, die zwischen diesen beiden Extremen liegen, so dass die Wahl einer geeigneten Datenbank schwieriger wird. Glücklicherweise haben NoSQL-Datenbanken wie MongoDB begonnen, SQL-ähnliche Optionen wie JOINs und Transaktionen zu übernehmen.

Umgekehrt bieten SQL-Datenbanken wie MySQL und PostgreSQL jetzt NoSQL-ähnliche JSON-Datenfelder an. Auch sie können deine Aufmerksamkeit verdienen, aber wie immer liegt die endgültige Entscheidung bei dir.