Kapitel 4. Die Abfragesprache von Cassandra
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In diesem Kapitel lernst du das Datenmodell von Cassandra kennen und erfährst, wie dieses Datenmodell durch die Cassandra Query Language (CQL) implementiert wird. Wir zeigen, wie CQL die Designziele von Cassandra unterstützt, und sehen uns einige allgemeine Verhaltensmerkmale an.
Für Entwickler und Administratoren, die aus der relationalen Welt kommen, kann das Datenmodell von Cassandra anfangs schwer zu verstehen sein. Einige Begriffe, wie z. B. Keyspace, sind völlig neu, und einige, wie z. B. Column, kommen in beiden Welten vor, haben aber leicht unterschiedliche Bedeutungen. Die Syntax von CQL ist in vielerlei Hinsicht ähnlich wie die von SQL, weist aber einige wichtige Unterschiede auf. Für diejenigen, die mit NoSQL-Technologien wie Dynamo oder Bigtable vertraut sind, kann es auch verwirrend sein, denn obwohl Cassandra auf diesen Technologien basiert, unterscheidet sich sein eigenes Datenmodell erheblich.
In diesem Kapitel beginnen wir also mit der Terminologie relationaler Datenbanken und stellen Cassandras Sicht auf die Welt vor. Auf dem Weg dorthin wirst du dich mit CQL vertraut machen und lernen, wie es dieses Datenmodell implementiert.
Das relationale Datenmodell
In einer relationalen Datenbank ist die Datenbank selbst der äußerste Container, der einer einzelnen Anwendung entsprechen könnte. Die Datenbank enthält Tabellen. Tabellen haben Namen und enthalten eine oder mehrere Spalten, die ebenfalls Namen haben. Wenn du Daten zu einer Tabelle hinzufügst, gibst du für jede definierte Spalte einen Wert an; wenn du für eine bestimmte Spalte keinen Wert hast, verwendest du null
. Dieser neue Eintrag fügt der Tabelle eine Zeile hinzu, die du später lesen kannst, wenn du den eindeutigen Bezeichner (Primärschlüssel) der Zeile kennst oder eine SQL-Anweisung verwendest, die einige Kriterien ausdrückt, die diese Zeile erfüllen könnte. Wenn du Werte in der Tabelle aktualisieren willst, kannst du alle Zeilen oder nur einige davon aktualisieren, je nachdem, welchen Filter du in einer "where"-Klausel deiner SQL-Anweisung verwendest.
Nach diesem Überblick bist du gut gerüstet, um das Datenmodell von Cassandra auf seine Gemeinsamkeiten und Unterschiede hin zu untersuchen.
Das Datenmodell von Cassandra
In diesem Abschnitt gehen wir von unten nach oben vor, um das Datenmodell von Cassandra zu verstehen.
Der einfachste Datenspeicher, den du dir vorstellen kannst, ist ein Array oder eine Liste. Er würde wie in Abbildung 4-1 aussehen.
Wenn du diese Liste persistieren würdest, könntest du sie später abfragen, aber du müsstest entweder jeden Wert untersuchen, um zu wissen, wofür er steht, oder jeden Wert immer an der gleichen Stelle in der Liste speichern und dann extern dokumentieren, welche Zelle im Array welche Werte enthält. Das würde bedeuten, dass du leere Platzhalterwerte (Nullen) angeben müsstest, um die vorgegebene Größe des Arrays beizubehalten, falls du keinen Wert für ein optionales Attribut hast (z. B. eine Faxnummer oder eine Wohnungsnummer). Ein Array ist eine eindeutig nützliche Datenstruktur, aber nicht semantisch reichhaltig.
Fügen wir nun eine zweite Dimension zu dieser Liste hinzu: Namen, die zu den Werten passen. Gib jeder Zelle einen Namen, und schon hast du eine Map-Struktur, wie in Abbildung 4-2 dargestellt.
Das ist eine Verbesserung, weil du die Namen deiner Werte kennen kannst. Wenn du also entscheidest, dass deine Karte Benutzerinformationen enthält, kannst du Spaltennamen wie first_name
, last_name
, phone
, email
und so weiter verwenden. Das ist eine etwas reichhaltigere Struktur, mit der du arbeiten kannst.
Aber die Struktur, die du bisher aufgebaut hast, funktioniert nur, wenn du eine Instanz einer bestimmten Entität hast, z. B. eine einzelne Person, einen Benutzer, ein Hotel oder einen Tweet. Sie bringt dir nicht viel, wenn du mehrere Entitäten mit der gleichen Struktur speichern willst, was du sicherlich tun willst. Es gibt nichts, um eine Sammlung von Name/Wert-Paaren zu vereinheitlichen, und keine Möglichkeit, dieselben Spaltennamen zu wiederholen. Du brauchst also etwas, mit dem du einige der Spaltenwerte in einer eindeutig adressierbaren Gruppe zusammenfassen kannst. Du brauchst einen Schlüssel, der auf eine Gruppe von Spalten verweist, die zusammen als eine Menge behandelt werden sollen. Du brauchst Zeilen. Wenn du eine einzelne Zeile erhältst, kannst du alle Name/Wert-Paare für eine einzelne Entität auf einmal abrufen oder nur die Werte für die Namen, an denen du interessiert bist. Diese Name/Wert-Paare kannst du als Spalten bezeichnen. Du könntest jede einzelne Entität, die einen Satz von Spalten enthält, als Zeile bezeichnen. Und der eindeutige Bezeichner für jede Zeile könnte als Zeilenschlüssel oder Primärschlüssel bezeichnet werden. Abbildung 4-3 zeigt den Inhalt einer einfachen Zeile: einen Primärschlüssel, der selbst eine oder mehrere Spalten ist, und weitere Spalten. Kommen wir kurz auf den Primärschlüssel zurück.
Cassandra definiert eine Tabelle als eine logische Unterteilung, die ähnliche Daten miteinander verbindet. Du könntest zum Beispiel eine user
Tabelle, eine hotel
Tabelle, eine address book
Tabelle und so weiter haben. Auf diese Weise ist eine Cassandra-Tabelle vergleichbar mit einer Tabelle in der relationalen Welt.
Du musst nicht jedes Mal einen Wert für jede Spalte speichern, wenn du eine neue Entität speicherst. Vielleicht kennst du nicht die Werte für jede Spalte einer bestimmten Entität. Manche Menschen haben zum Beispiel eine zweite Telefonnummer, andere nicht, und in einem Online-Formular, das von Cassandra unterstützt wird, gibt es vielleicht einige Felder, die optional sind, und einige, die erforderlich sind. Das ist in Ordnung. Anstatt null
für die Werte zu speichern, die du nicht kennst, und damit Platz zu verschwenden, speicherst du diese Spalte für diese Zeile einfach gar nicht. Jetzt hast du also eine spärliche, mehrdimensionale Array-Struktur, die wie in Abbildung 4-4 aussieht. Diese flexible Datenstruktur ist charakteristisch für Cassandra und andere Datenbanken, die als breite Spaltenspeicher gelten.
Kehren wir nun zur Diskussion über Primärschlüssel in Cassandra zurück, denn dies ist ein grundlegendes Thema, das dein Verständnis der Architektur und des Datenmodells von Cassandra, der Art und Weise, wie Cassandra Daten liest und schreibt, und der Skalierbarkeit von Cassandra beeinflusst.
Cassandra verwendet eine besondere Art von Primärschlüssel, den sogenannten zusammengesetzten Schlüssel (Composite Key ), um Gruppen zusammengehöriger Zeilen, auch Partitionen genannt, darzustellen. Der zusammengesetzte Schlüssel besteht aus einem Partitionsschlüssel und einem optionalen Satz von Clustering-Spalten. Der Partitionsschlüssel wird verwendet, um die Knoten zu bestimmen, auf denen die Zeilen gespeichert werden, und kann selbst aus mehreren Spalten bestehen. Mit den Clustering-Spalten wird gesteuert, wie die Daten für die Speicherung innerhalb einer Partition sortiert werden. Cassandra unterstützt auch ein zusätzliches Konstrukt namens statische Spalte, in der Daten gespeichert werden, die nicht Teil des Primärschlüssels sind, aber von jeder Zeile in einer Partition geteilt werden.
Abbildung 4-5 zeigt, wie jede Partition durch einen Partitionsschlüssel eindeutig identifiziert wird und wie die Clustering-Schlüssel zur eindeutigen Identifizierung der Zeilen innerhalb einer Partition verwendet werden. Wenn keine Clustering-Spalten vorhanden sind, besteht jede Partition aus einer einzigen Zeile.
Wenn wir diese Konzepte zusammenfassen, erhalten wir die grundlegenden Datenstrukturen von Cassandra:
-
Die Spalte, die ein Name/Wert-Paar ist
-
Die Zeile, die ein Container für Spalten ist, die durch einen Primärschlüssel referenziert werden
-
Die Partition, d.h. eine Gruppe zusammengehöriger Zeilen, die gemeinsam auf denselben Knoten gespeichert werden
-
Die Tabelle, die ein Container für Zeilen ist, die durch Partitionen organisiert sind
-
Der Keyspace, der ein Container für Tabellen ist
-
Der Cluster ist ein Container für Keyspaces, der sich über einen oder mehrere Knotenpunkte erstreckt.
Das ist also der Bottom-up-Ansatz, um das Datenmodell von Cassandra zu betrachten. Da du nun die grundlegende Terminologie kennst, wollen wir uns die einzelnen Strukturen genauer ansehen.
Clusters
Wie bereits erwähnt, ist die Cassandra-Datenbank so konzipiert, dass sie auf mehrere Maschinen verteilt ist, die zusammen arbeiten und für den Endnutzer als eine einzige Instanz erscheinen. Die äußerste Struktur in Cassandra ist also der Cluster, der manchmal auch als Ring bezeichnet wird, weil Cassandra die Daten den Knoten im Cluster zuweist, indem es sie in einem Ring anordnet.
Keyspaces
Ein Cluster ist ein Container für Keyspaces. Ein Keyspace ist der äußerste Container für Daten in Cassandra und entspricht in etwa einer Datenbank im relationalen Modell. Genauso wie eine Datenbank ein Container für Tabellen im relationalen Modell ist, ist ein Keyspace ein Container für Tabellen im Cassandra-Datenmodell. Wie eine relationale Datenbank hat ein Keyspace einen Namen und eine Reihe von Attributen, die das Keyspace-weite Verhalten wie die Replikation definieren.
Da wir uns im Moment auf das Datenmodell konzentrieren, werden wir Fragen zur Einrichtung und Konfiguration von Clustern und Keyspaces auf später verschieben. Wir werden diese Themen in Kapitel 10 behandeln.
Tische
Eine Tabelle ist ein Container für eine geordnete Sammlung von Zeilen, von denen jede selbst eine geordnete Sammlung von Spalten ist. Zeilen werden in Partitionen organisiert und den Knoten in einem Cassandra-Cluster entsprechend der Spalte(n), die als Partitionsschlüssel bezeichnet werden, zugewiesen. Die Reihenfolge der Daten innerhalb einer Partition wird durch die Clustering-Spalten bestimmt.
Wenn du Daten in eine Tabelle in Cassandra schreibst, gibst du Werte für eine oder mehrere Spalten an. Diese Sammlung von Werten wird als Zeile bezeichnet. Du musst für jede der im Primärschlüssel enthaltenen Spalten einen Wert angeben, da diese Spalten zusammengenommen die Zeile eindeutig identifizieren.
Gehen wir zurück zur Tabelle user
aus dem vorherigen Kapitel. Erinnere dich daran, wie du eine Datenzeile geschrieben und sie dann mit dem Befehl SELECT
in cqlsh
gelesen hast:
cqlsh:my_keyspace> SELECT * FROM user where last_name = 'Nguyen'; last_name | first_name | title -----------+------------+------- Nguyen | Bill | Mr. (1 rows)
In der letzten Zeile der Ausgabe siehst du, dass eine Zeile zurückgegeben wurde. Es handelt sich um die Zeile, die durch last_name
"Nguyen" und first_name
"Bill" identifiziert wird. Dies ist der Primärschlüssel, der diese Zeile eindeutig identifiziert.
Ein interessanter Punkt bei der vorangegangenen Abfrage ist, dass sie nur den Partitionsschlüssel angibt, was sie zu einer Abfrage macht, die potenziell mehrere Zeilen zurückgeben kann. Um dies zu verdeutlichen, fügen wir einen weiteren Benutzer mit demselben last_name
hinzu und wiederholen dann den SELECT
Befehl von oben:
cqlsh:my_keyspace> INSERT INTO user (first_name, last_name, title) VALUES ('Wanda', 'Nguyen', 'Mrs.'); cqlsh:my_keyspace> SELECT * FROM user WHERE last_name='Nguyen'; last_name | first_name | title -----------+------------+------- Nguyen | Bill | Mr. Nguyen | Wanda | Mrs. (2 rows)
Wie du siehst, hast du durch die Partitionierung der Benutzer nach last_name
die Möglichkeit geschaffen, die gesamte Partition in einer einzigen Abfrage zu laden, indem du die last_name
bereitstellst. Um nur auf eine einzige Zeile zuzugreifen, müsstest du den gesamten Primärschlüssel angeben:
cqlsh:my_keyspace> SELECT * FROM user WHERE last_name='Nguyen' AND first_name='Bill'; last_name | first_name | title -----------+------------+------- Nguyen | Bill | Mr. (1 rows)
Datenzugriff erfordert einen Primärschlüssel
Um dieses wichtige Detail zusammenzufassen: Die Befehle SELECT
, INSERT
, UPDATE
und DELETE
in CQL beziehen sich alle auf Zeilen. Bei den Befehlen INSERT
und UPDATE
müssen alle Primärschlüsselspalten mit der Klausel WHERE
angegeben werden, um die betroffene Zeile zu identifizieren. Die Befehle SELECT
und DELETE
können sich auf eine oder mehrere Zeilen innerhalb einer Partition, eine ganze Partition oder sogar mehrere Partitionen beziehen, indem sie die Klauseln WHERE
und IN
verwenden. Wir werden diese Befehle in Kapitel 9 näher erläutern.
Während du für jede Primärschlüsselspalte einen Wert angeben musst, wenn du der Tabelle eine neue Zeile hinzufügst, bist du nicht verpflichtet, Werte für Nicht-Primärschlüsselspalten anzugeben. Um dies zu veranschaulichen, fügen wir eine weitere Zeile ohne title
ein:
cqlsh:my_keyspace> INSERT INTO user (first_name, last_name) ... VALUES ('Mary', 'Rodriguez'); cqlsh:my_keyspace> SELECT * FROM user WHERE last_name='Rodriguez'; last_name | first_name | title -----------+------------+------- Rodriguez | Mary | null (1 rows)
Da du keinen Wert für title
angegeben hast, wird der Wert null
zurückgegeben.
Wenn du dich später entscheidest, dass du auch die mittleren Initialen der Benutzer behalten möchtest, kannst du die Tabelle user
mit dem Befehl ALTER TABLE
ändern und die Ergebnisse dann mit dem Befehl DESCRIBE TABLE
anzeigen:
cqlsh:my_keyspace> ALTER TABLE user ADD middle_initial text; cqlsh:my_keyspace> DESCRIBE TABLE user; CREATE TABLE my_keyspace.user ( last_name text, first_name text, middle_initial text, title text, PRIMARY KEY (last_name, first_name) ) ...
Du siehst, dass die Spalte middle_initial
hinzugefügt worden ist. Beachte, dass wir die Ausgabe gekürzt haben, um die verschiedenen Tabelleneinstellungen wegzulassen. Im weiteren Verlauf des Buches erfährst du mehr über diese Einstellungen und wie du sie konfigurierst.
Lass uns jetzt ein paar zusätzliche Zeilen schreiben, verschiedene Spalten für jede ausfüllen und die Ergebnisse lesen:
cqlsh:my_keyspace> INSERT INTO user (first_name, middle_initial, last_name, title) VALUES ('Bill', 'S', 'Nguyen', 'Mr.'); cqlsh:my_keyspace> INSERT INTO user (first_name, middle_initial, last_name, title) VALUES ('Bill', 'R', 'Nguyen', 'Mr.'); cqlsh:my_keyspace> SELECT * FROM user WHERE first_name='Bill' AND last_name='Nguyen'; last_name | first_name | middle_initial | title -----------+------------+----------------+------- Nguyen | Bill | R | Mr. (1 rows)
War das das Ergebnis, das du erwartet hast? Wenn du genau hingesehen hast, hast du vielleicht bemerkt, dass beide INSERT
Anweisungen hier eine vorherige Zeile angeben, die eindeutig durch die Primärschlüsselspalten first_name
und last_name
identifiziert wird. Infolgedessen hat Cassandra die von dir angegebene Zeile getreu aktualisiert, und deine SELECT
gibt nur die eine Zeile zurück, die mit diesem Primärschlüssel übereinstimmt. Die beiden INSERT
Anweisungen haben nur dazu gedient, die middle_initial
zuerst zu setzen und dann zu überschreiben.
Einfügen, Aktualisieren und Upsert
Da Cassandra ein Append-Modell verwendet, gibt es keinen grundlegenden Unterschied zwischen Einfüge- und Aktualisierungsvorgängen. Wenn du eine Zeile einfügst, die denselben Primärschlüssel wie eine bestehende Zeile hat, wird die Zeile ersetzt. Wenn du eine Zeile aktualisierst und der Primärschlüssel nicht vorhanden ist, erstellt Cassandra ihn.
Aus diesem Grund wird oft gesagt, dass Cassandra Upsert unterstützt, was bedeutet, dass Inserts und Updates gleich behandelt werden, mit einer kleinen Ausnahme, die wir in "Lightweight Transactions" besprechen werden .
Die Daten, die du bis zu diesem Punkt eingefügt hast, werden in Abbildung 4-6 dargestellt. Du siehst, dass es zwei Partitionen gibt, die durch die last_name
Werte "Nguyen" und "Rodriguez" gekennzeichnet sind. Die Partition "Nguyen" enthält die beiden Zeilen "Bill" und "Wanda", und die Zeile für "Bill" enthält Werte in den Spalten title
und middle_initial
, während für "Wanda" nur ein title
und kein middle_initial
angegeben ist.
Nachdem du nun mehr über die Struktur einer Tabelle erfahren und einige Datenmodelle erstellt hast, wollen wir uns nun mit den Spalten beschäftigen.
Rubriken
Eine Spalte ist die grundlegendste Einheit der Datenstruktur im Cassandra-Datenmodell. Bis jetzt hast du gesehen, dass eine Spalte einen Namen und einen Wert enthält. Bei der Definition der Spalte legst du fest, dass jeder der Werte einen bestimmten Typ haben muss. Du wirst die verschiedenen Typen, die für jede Spalte verfügbar sind, noch genauer kennenlernen wollen, aber zuerst wollen wir uns ein paar andere Attribute einer Spalte ansehen, die wir noch nicht besprochen haben: Zeitstempel und Time to Live. Diese Attribute sind wichtig, um zu verstehen, wie Cassandra die Zeit nutzt, um Daten aktuell zu halten.
Zeitstempel
Jedes Mal, wenn du Daten in Cassandra schreibst, wird für jeden Spaltenwert, der eingefügt oder aktualisiert wird, ein Zeitstempel in Mikrosekunden erzeugt. Intern verwendet Cassandra diese Zeitstempel, um widersprüchliche Änderungen am gleichen Wert aufzulösen, was häufig als "last write wins" -Ansatz bezeichnet wird.
Sehen wir uns die Zeitstempel an, die für frühere Schreibvorgänge generiert wurden, indem wir die Funktion writetime()
zum Befehl SELECT
für die Spalte title
hinzufügen, plus ein paar andere Werte für den Kontext:
cqlsh:my_keyspace> SELECT first_name, last_name, title, writetime(title) FROM user; first_name | last_name | title | writetime(title) ------------+-----------+-------+------------------ Mary | Rodriguez | null | null Bill | Nguyen | Mr. | 1567876680189474 Wanda | Nguyen | Mrs. | 1567874109804754 (3 rows)
Wie du vielleicht erwartest, gibt es keinen Zeitstempel für eine Spalte, die nicht gesetzt wurde. Du könntest erwarten, dass du ein ähnliches Ergebnis wie für die Spalte title
erhältst, wenn du nach dem Zeitstempel für first_name
oder last_name
fragst. Es stellt sich jedoch heraus, dass es nicht erlaubt ist, den Zeitstempel für Primärschlüsselspalten abzufragen:
cqlsh:my_keyspace> SELECT WRITETIME(first_name) FROM user; InvalidRequest: code=2200 [Invalid query] message="Cannot use selection function writeTime on PRIMARY KEY part first_name"
In Cassandra kannst du auch einen Zeitstempel angeben, den du bei Schreibvorgängen verwenden möchtest. Dazu verwendest du zum ersten Mal den CQL-Befehl UPDATE
. Verwende die optionale Option USING TIMESTAMP
, um manuell einen Zeitstempel zu setzen (beachte, dass der Zeitstempel später sein muss als der von deinem SELECT
Befehl, sonst wird UPDATE
ignoriert):
cqlsh:my_keyspace> UPDATE user USING TIMESTAMP 1567886623298243 SET middle_initial = 'Q' WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT first_name, middle_initial, last_name, WRITETIME(middle_initial) FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; first_name | middle_initial | last_name | writetime(middle_initial) ------------+----------------+-----------+--------------------------- Mary | Q | Rodriguez | 1567886623298243 (1 rows)
Diese Anweisung bewirkt, dass die Spalte middle_initial
hinzugefügt und der Zeitstempel auf den von dir angegebenen Wert gesetzt wird.
Arbeiten mit Zeitstempeln
Das Setzen des Zeitstempels ist für Schreibvorgänge nicht erforderlich. Diese Funktion wird in der Regel für Schreibvorgänge verwendet, bei denen die Gefahr besteht, dass einige der Schreibvorgänge dazu führen, dass frische Daten mit veralteten Daten überschrieben werden. Dies ist ein fortgeschrittenes Verhalten und sollte mit Vorsicht verwendet werden.
Es gibt derzeit keine Möglichkeit, die von writetime()
erzeugten Zeitstempel in ein freundlicheres Format in cqlsh
zu konvertieren.
Lebenszeit (TTL)
Eine sehr leistungsstarke Funktion, die Cassandra bietet, ist die Möglichkeit, Daten, die nicht mehr benötigt werden, verfallen zu lassen. Dieses Verfallsdatum ist sehr flexibel und funktioniert auf der Ebene einzelner Spaltenwerte. Die Time to Live (oder TTL) ist ein Wert, den Cassandra für jeden Spaltenwert speichert, um anzugeben, wie lange der Wert aufbewahrt werden soll.
Der TTL-Wert ist standardmäßig auf null
eingestellt, was bedeutet, dass die geschriebenen Daten nicht ablaufen. Wir zeigen das, indem wir die Funktion TTL()
zu einem SELECT
Befehl in cqlsh
hinzufügen, um den TTL-Wert für Marys Titel zu sehen:
cqlsh:my_keyspace> SELECT first_name, last_name, TTL(title) FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; first_name | last_name | ttl(title) ------------+-----------+------------ Mary | Rodriguez | null (1 rows)
Jetzt setzen wir die TTL für die Spalte middle_initial
auf eine Stunde (3.600 Sekunden), indem wir die Option USING TTL
zu deinem Befehl UPDATE
hinzufügen:
cqlsh:my_keyspace> UPDATE user USING TTL 3600 SET middle_initial = 'Z' WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT first_name, middle_initial, last_name, TTL(middle_initial) FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; first_name | middle_initial | last_name | ttl(middle_initial) ------------+----------------+-----------+--------------------- Mary | Z | Rodriguez | 3574 (1 rows)
Wie du siehst, zählt die Uhr bereits deine TTL herunter, was die paar Sekunden widerspiegelt, die du für die Eingabe des zweiten Befehls gebraucht hast. Wenn du diesen Befehl in einer Stunde erneut ausführst , wird Marys middle_initial
als null
angezeigt. Du kannst die TTL für INSERTS
auch mit der gleichen Option USING TTL
setzen. In diesem Fall läuft die gesamte Zeile ab.
Du kannst versuchen, eine Zeile mit einer TTL von 60 Sekunden einzufügen und überprüfen, ob die Zeile anfänglich vorhanden ist:
cqlsh:my_keyspace> INSERT INTO user (first_name, last_name) VALUES ('Jeff', 'Carpenter') USING TTL 60; cqlsh:my_keyspace> SELECT * FROM user WHERE first_name='Jeff' AND last_name='Carpenter'; last_name | first_name | middle_initial | title -----------+------------+----------------+------- Carpenter | Jeff | null | null (1 rows)
Nachdem du eine Minute gewartet hast, ist die Zeile nicht mehr da:
cqlsh:my_keyspace> SELECT * FROM user WHERE first_name='Jeff' AND last_name='Carpenter'; last_name | first_name | middle_initial | title -----------+------------+----------------+------- (0 rows)
TTL verwenden
Erinnere dich daran, dass die TTL für Nicht-Primärschlüsselspalten auf Spaltenebene gespeichert wird. Es gibt derzeit keinen Mechanismus, um die TTL auf Zeilenebene direkt nach dem ersten Einfügen zu setzen; stattdessen müsstest du die Zeile erneut einfügen und dabei das Upsert-Verhalten von Cassandra nutzen. Wie beim Zeitstempel gibt es keine Möglichkeit, den TTL-Wert einer Primärschlüsselspalte zu erhalten oder zu setzen, und die TTL kann nur für eine Spalte gesetzt werden, wenn du einen Wert für die Spalte angibst.
Das Verhalten von Cassandras TTL-Funktion kann etwas unintuitiv sein, vor allem, wenn du eine bestehende Zeile aktualisierst. Rahul Kumars Blog "Cassandra TTL intricacies and usage by examples" fasst die Auswirkungen von TTL in einer Reihe von Fällen sehr gut zusammen.
CQL-Typen
Nachdem wir nun einen tieferen Einblick in die Art und Weise bekommen haben, wie Cassandra Spalten darstellt, einschließlich zeitbasierter Metadaten, wollen wir uns nun die verschiedenen Typen ansehen, die dir für die Darstellung von Werten zur Verfügung stehen.
Wie du bereits gesehen hast, hat jede Spalte in einer Tabelle einen bestimmten Typ. Bis jetzt hast du nur den Typ varchar
verwendet, aber es gibt noch viele andere Optionen in CQL, die wir uns ansehen wollen.
CQL unterstützt eine flexible Reihe von Datentypen, darunter einfache Zeichen- und numerische Typen, Sammlungen und benutzerdefinierte Typen. Wir werden diese Datentypen beschreiben und einige Beispiele für ihre Verwendung geben, damit du lernst, die richtige Wahl für dein Datenmodell zu treffen.
Numerische Datentypen
CQL unterstützt die numerischen Typen, die du erwarten würdest, darunter Ganzzahl- und Gleitkommazahlen. Diese Typen sind den Standardtypen in Java und anderen Sprachen ähnlich:
int
bigint
-
Eine 64-Bit-lange Ganzzahl mit Vorzeichen (entspricht einer Java
long
) smallint
-
Eine 16-Bit-Ganzzahl mit Vorzeichen (entspricht einer Java
short
) tinyint
varint
-
Eine vorzeichenbehaftete Ganzzahl mit variabler Genauigkeit (entspricht
java.math.BigInteger
) float
double
decimal
-
Ein Dezimalwert mit variabler Genauigkeit (entspricht
java.math.BigDecimal
)
Zusätzliche Integer-Typen
Die Typen smallint
und tinyint
wurden in der Version Cassandra 2.2 hinzugefügt.
Während Aufzählungstypen in vielen Sprachen üblich sind, gibt es in CQL keine direkte Entsprechung. Eine gängige Praxis ist es, Aufzählungswerte als Strings zu speichern. In Java könntest du zum Beispiel die Methode Enum.name()
verwenden, um einen Aufzählungswert in String
umzuwandeln, um ihn als Text auf Cassandra zu schreiben, und die Methode Enum.valueOf()
, um den Text wieder in den Aufzählungswert umzuwandeln.
Textuelle Datentypen
CQL stellt zwei Datentypen für die Darstellung von Text zur Verfügung, von denen du schon ziemlich viel Gebrauch gemacht hast (text
):
UTF-8 ist der neuere und am weitesten verbreitete Textstandard und unterstützt die Internationalisierung. Daher empfehlen wir, text
statt ascii
zu verwenden, wenn du Tabellen für neue Daten erstellst. Der Typ ascii
ist am nützlichsten, wenn du mit alten Daten im ASCII-Format arbeitest .
Einstellen des Gebietsschemas in cqlsh
Standardmäßig gibt cqlsh
Steuerzeichen und andere nicht druckbare Zeichen mit einem Backslash-Escape aus. Du kannst steuern, wie cqlsh
Nicht-ASCII-Zeichen anzeigt, indem du die Locale mit der Umgebungsvariablen $LANG
einstellst, bevor du das Tool ausführst. Weitere Informationen findest du unter dem Befehl cqlsh
HELP TEXT_OUTPUT
.
Zeit- und Identitätsdatentypen
Die Identität von Datenelementen wie Zeilen und Partitionen ist in jedem Datenmodell wichtig, damit der Zugriff auf die Daten möglich ist. Cassandra bietet mehrere Typen, die sich als sehr nützlich erweisen, um eindeutige Partitionsschlüssel zu definieren. Nehmen wir uns ein wenig Zeit, um sie zu erforschen (Wortspiel beabsichtigt):
timestamp
-
Wir haben bereits erwähnt, dass jede Spalte einen Zeitstempel hat, der angibt, wann sie zuletzt geändert wurde. Die Zeit kann als 64-Bit-Ganzzahl mit Vorzeichen kodiert werden, aber in der Regel ist es viel nützlicher, einen Zeitstempel in einem der verschiedenen unterstützten ISO 8601 Datumsformate einzugeben. Zum Beispiel:
2015-06-15 20:05-0700 2015-06-15 20:05:07-0700 2015-06-15 20:05:07.013-0700 2015-06-15T20:05-0700 2015-06-15T20:05:07-0700 2015-06-15T20:05:07.013+-0700
Die bewährte Methode besteht darin, immer Zeitzonen anzugeben, anstatt sich auf die Zeitzonenkonfiguration des Betriebssystems zu verlassen.
date, time
-
In den Versionen bis Cassandra 2.1 gab es nur den Typ
timestamp
zur Darstellung von Zeiten, die sowohl ein Datum als auch eine Uhrzeit enthielten. Mit der Version 2.2 wurden die Typendate
undtime
eingeführt, mit denen diese unabhängig voneinander dargestellt werden können, d.h. ein Datum ohne Uhrzeit und eine Tageszeit ohne Bezug zu einem bestimmten Datum. Wietimestamp
unterstützen auch diese Typen die ISO 8601-Formate.Obwohl es in Java 8 neue
java.time
Typen gibt, entspricht derdate
Typ einem benutzerdefinierten Typ in Cassandra, um die Kompatibilität mit älteren JDKs zu wahren. Der Typtime
entspricht einem Javalong
, der die Anzahl der Nanosekunden seit Mitternacht angibt. uuid
-
Ein universell eindeutiger Bezeichner (UUID) ist ein 128-Bit-Wert, bei dem die Bits einem von mehreren Typen entsprechen, von denen die am häufigsten verwendeten als Typ 1 und Typ 4 bekannt sind. Die CQL
uuid
ist eine UUID des Typs 4, die vollständig auf Zufallszahlen basiert. UUIDs werden in der Regel als Bindestrich-getrennte Sequenzen von Hex-Ziffern dargestellt. Zum Beispiel:1a6300ca-0572-4736-a393-c0b7229e193e
Der Typ
uuid
wird oft als Surrogatschlüssel verwendet, entweder allein oder in Kombination mit anderen Werten.Da UUIDs eine endliche Länge haben, ist ihre Eindeutigkeit nicht absolut garantiert. Die meisten Betriebssysteme und Programmiersprachen bieten jedoch Hilfsprogramme zur Erzeugung von IDs, die eine ausreichende Eindeutigkeit gewährleisten. Du kannst auch einen UUID-Wert vom Typ 4 über die CQL-Funktion
uuid()
erhalten und diesen Wert in einerINSERT
oderUPDATE
verwenden. timeuuid
-
Dies ist eine UUID des Typs 1, die auf der MAC-Adresse des Computers, der Systemzeit und einer Sequenznummer basiert, um Duplikate zu verhindern. Dieser Typ wird häufig als konfliktfreier Zeitstempel verwendet. CQL bietet mehrere Komfortfunktionen für die Interaktion mit dem Typ
timeuuid
:now()
,dateOf()
undunixTimestampOf()
.Die Verfügbarkeit dieser Komfortfunktionen ist ein Grund, warum
timeuuid
tendenziell häufiger genutzt wird alsuuid
.
Ausgehend von den vorherigen Beispielen könntest du feststellen, dass du jedem Benutzer eine eindeutige ID zuweisen möchtest, da first_name
vielleicht kein ausreichend eindeutiger Schlüssel für die Tabelle user
ist. Schließlich ist es sehr wahrscheinlich, dass du irgendwann auf Benutzer mit demselben Vornamen triffst. Wenn du ganz neu anfangen würdest, könntest du diese Kennung zum Primärschlüssel machen, aber im Moment fügst du sie als weitere Spalte hinzu.
Primärschlüssel sind für immer
Nachdem du eine Tabelle erstellt hast, gibt es keine Möglichkeit mehr, den Primärschlüssel zu ändern, da dieser steuert, wie die Daten innerhalb des Clusters verteilt werden und - was noch wichtiger ist - wie sie auf der Festplatte gespeichert werden.
Fügen wir den Bezeichner mit einem uuid
hinzu:
cqlsh:my_keyspace> ALTER TABLE user ADD id uuid;
Als Nächstes fügst du mit der Funktion uuid()
eine ID für Mary ein und siehst dir dann die Ergebnisse an:
cqlsh:my_keyspace> UPDATE user SET id = uuid() WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT first_name, id FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; first_name | id ------------+-------------------------------------- Mary | ebf87fee-b372-4104-8a22-00c1252e3e05 (1 rows)
Beachte, dass die id
im UUID-Format ist.
Jetzt hast du ein stabileres Tabellendesign, das du mit noch mehr Spalten erweitern kannst, wenn du mehr Typen kennenlernst.
Andere einfache Datentypen
CQL bietet mehrere andere einfache Datentypen, die nicht in eine der oben genannten Kategorien passen:
boolean
-
Dies ist ein einfacher true/false Wert.
cqlsh
akzeptiert diese Werte unabhängig von der Groß-/Kleinschreibung und gibtTrue
oderFalse
aus. blob
-
Ein binäres großes Objekt (Blob) ist ein umgangssprachlicher Begriff für eine beliebige Anordnung von Bytes. Der CQL-Blob-Typ ist nützlich, um Medien oder andere binäre Dateitypen zu speichern. Cassandra validiert oder überprüft die Bytes in einem Blob nicht. CQL stellt die Daten als hexadezimale Ziffern dar - zum Beispiel
0x00000ab83cf0
. Wenn du beliebige Textdaten in den Blob kodieren willst, kannst du die FunktiontextAsBlob()
verwenden, um Werte für die Eingabe anzugeben. Weitere Informationen findest du in dercqlsh
HilfefunktionHELP BLOB_INPUT
. inet
-
Dieser Typ stellt IPv4- oder IPv6-Internetadressen dar.
cqlsh
akzeptiert jedes zulässige Format zur Definition von IPv4-Adressen, einschließlich punktierter oder nicht punktierter Darstellungen mit Dezimal-, Oktal- oder Hexadezimalwerten. In der Ausgabe voncqlsh
werden die Werte jedoch im punktierten Dezimalformat dargestellt - zum Beispiel192.0.2.235
.IPv6-Adressen werden als acht Gruppen von vier hexadezimalen Ziffern dargestellt, die durch Doppelpunkte getrennt sind - zum Beispiel
2001:0db8:85a3:0000:0000:8a2e:0370:7334
. Die IPv6-Spezifikation erlaubt es, aufeinanderfolgende Nullen in Hexadezimalzahlen zusammenzufassen, so dass der vorangehende Wert wie folgt dargestellt wird, wenn er mitSELECT
gelesen wird:2001: db8:85a3:a::8a2e:370:7334
. counter
-
Der Datentyp
counter
stellt eine 64-Bit-Ganzzahl mit Vorzeichen zur Verfügung, deren Wert nicht direkt gesetzt, sondern nur inkrementiert oder dekrementiert werden kann. Cassandra ist eine der wenigen Datenbanken, die eine rennfreie Inkrementierung über Rechenzentren hinweg ermöglicht. Zähler werden häufig verwendet, um Statistiken wie die Anzahl der Seitenaufrufe, Tweets, Logmeldungen usw. zu erfassen. Der Typcounter
hat einige besondere Einschränkungen. Er kann nicht als Teil eines Primärschlüssels verwendet werden. Wenn ein Zähler verwendet wird, müssen alle Spalten, die keine Primärschlüsselspalten sind, Zähler sein.Du könntest zum Beispiel eine zusätzliche Tabelle erstellen, um zu zählen, wie oft ein Nutzer eine Website besucht hat:
cqlsh:my_keyspace> CREATE TABLE user_visits ( user_id uuid PRIMARY KEY, visits counter);
Du würdest dann den Wert für die Benutzerin "Mary" jedes Mal, wenn sie die Website besucht, entsprechend der zuvor zugewiesenen eindeutigen ID erhöhen:
cqlsh:my_keyspace> UPDATE user_visits SET visits = visits + 1 WHERE user_id=ebf87fee-b372-4104-8a22-00c1252e3e05;
Und du kannst den Wert des Zählers genauso auslesen wie jede andere Spalte:
cqlsh:my_keyspace> SELECT visits from user_visits WHERE user_id=ebf87fee-b372-4104-8a22-00c1252e3e05; visits -------- 1 (1 rows)
Es gibt keine Möglichkeit, einen Zähler direkt zurückzusetzen, aber du kannst ihn annähernd zurücksetzen, indem du den Zählerwert liest und um diesen Wert dekrementierst. Leider ist dies keine Garantie dafür, dass es perfekt funktioniert, da der Zähler zwischen dem Lesen und Schreiben an anderer Stelle geändert worden sein kann.
Eine Warnung vor Ödipotenz
Die Operatoren zum Erhöhen und Verringern des Zählers sind nicht idempotent. Eine idempotente Operation ist eine Operation, die bei mehrfacher Ausführung das gleiche Ergebnis liefert. Inkrementieren und Dekrementieren sind nicht idempotent, weil ihre mehrfache Ausführung zu unterschiedlichen Ergebnissen führen kann, wenn der gespeicherte Wert erhöht oder verringert wird.
Um zu sehen, wie das möglich ist, bedenke, dass Cassandra ein verteiltes System ist, in dem Interaktionen über ein Netzwerk fehlschlagen können, wenn ein Knoten nicht auf eine Anfrage antwortet, die Erfolg oder Misserfolg anzeigt. Eine typische Reaktion des Clients auf diese Anfrage ist die Wiederholung des Vorgangs. Das Ergebnis des erneuten Versuchs einer nicht-idempotenten Operation wie dem Erhöhen eines Zählers ist nicht vorhersehbar. Da nicht bekannt ist, ob der erste Versuch erfolgreich war, kann der Wert zweimal erhöht worden sein. Das ist kein fataler Fehler, aber etwas, das du bei der Verwendung von Zählern beachten solltest.
Die einzige andere CQL-Operation, die nicht idempotent ist, außer dem Erhöhen oder Verringern eines Zählers, ist das Hinzufügen eines Eintrags zu einer list
, was wir als nächstes besprechen werden.
Sammlungen
Angenommen, du möchtest die Tabelle user
erweitern, um mehrere E-Mail-Adressen zu unterstützen. Eine Möglichkeit wäre, zusätzliche Spalten zu erstellen, z. B. email2
, email3
und so weiter. Dieser Ansatz funktioniert zwar, lässt sich aber nicht sehr gut skalieren und könnte viel Nacharbeit verursachen. Es ist viel einfacher, die E-Mail-Adressen als eine Gruppe oder "Sammlung" zu behandeln. CQL bietet drei Sammlungstypen, die dir in solchen Situationen helfen: Sets, Listen und Maps. Schauen wir uns diese Typen einmal an:
set
-
Der Datentyp
set
speichert eine Sammlung von Elementen. Die Elemente sind beim Speichern ungeordnet, werden aber in sortierter Reihenfolge zurückgegeben. Zum Beispiel werden Textwerte in alphabetischer Reihenfolge zurückgegeben. Mengen können die einfachen Typen enthalten, die du bereits kennengelernt hast, aber auch benutzerdefinierte Typen (auf die wir gleich noch eingehen werden) und sogar andere Sammlungen. Ein Vorteil der Verwendung vonset
ist die Möglichkeit, zusätzliche Elemente einzufügen, ohne den Inhalt zuerst lesen zu müssen.Du kannst die Tabelle
user
ändern, um eine Reihe von E-Mail-Adressen hinzuzufügen:cqlsh:my_keyspace> ALTER TABLE user ADD emails set<text>;
Füge dann eine E-Mail-Adresse für Mary hinzu und überprüfe, ob sie erfolgreich hinzugefügt wurde:
cqlsh:my_keyspace> UPDATE user SET emails = { 'mary@example.com' } WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT emails FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; emails ---------------------- {'mary@example.com'} (1 rows)
Beachte, dass du beim Hinzufügen der ersten E-Mail-Adresse den vorherigen Inhalt der Menge ersetzt hast, der in diesem Fall
null
war. Du kannst später eine weitere E-Mail-Adresse hinzufügen, ohne die gesamte Menge zu ersetzen, indem du die Verkettung verwendest:cqlsh:my_keyspace> UPDATE user SET emails = emails + {'mary.rodriguez.AZ@gmail.com' } WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT emails FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; emails --------------------------------------------------- {'mary.mcdonald.AZ@gmail.com', 'mary@example.com'} (1 rows)
Andere Mengenoperationen
Du kannst auch Elemente aus der Menge löschen, indem du den Subtraktionsoperator verwendest:
SET emails = emails - {'mary@example.com'}
.Alternativ kannst du auch die gesamte Menge löschen, indem du die Notation der leeren Menge verwendest:
SET emails = {}
. list
-
Der Datentyp
list
enthält eine geordnete Liste von Elementen. Standardmäßig werden die Werte in der Reihenfolge des Einfügens gespeichert. Du kannst die Tabelleuser
ändern, um eine Liste von Telefonnummern hinzuzufügen:cqlsh:my_keyspace> ALTER TABLE user ADD phone_numbers list<text>;
Füge dann eine Telefonnummer für Mary hinzu und überprüfe, ob sie erfolgreich hinzugefügt wurde:
cqlsh:my_keyspace> UPDATE user SET phone_numbers = ['1-800-999-9999' ] WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; phone_numbers -------------------- ['1-800-999-9999'] (1 rows)
Lass uns eine zweite Zahl hinzufügen, indem wir sie anhängen:
cqlsh:my_keyspace> UPDATE user SET phone_numbers = phone_numbers + [ '480-111-1111' ] WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; phone_numbers ------------------------------------ ['1-800-999-9999', '480-111-1111'] (1 rows)
Die zweite Nummer, die du hinzugefügt hast, erscheint nun am Ende der Liste.
Hinweis
Du hättest die Nummer auch an den Anfang der Liste stellen können, indem du die Reihenfolge der Werte umkehrst:
SET phone_numbers = [‘4801234567'] + phone_numbers
.Du kannst ein einzelnes Element in der Liste ersetzen, wenn du es über seinen Index referenzierst:
cqlsh:my_keyspace> UPDATE user SET phone_numbers[1] = '480-111-1111' WHERE first_name = 'Mary' AND last_name = 'Rodriguez';
Wie bei Mengen kannst du auch den Subtraktionsoperator verwenden, um Elemente zu entfernen, die einem bestimmten Wert entsprechen:
cqlsh:my_keyspace> UPDATE user SET phone_numbers = phone_numbers - [ '480-111-1111' ] WHERE first_name = 'Mary' AND last_name = 'Rodriguez';
Schließlich kannst du ein bestimmtes Element direkt über seinen Index löschen:
cqlsh:my_keyspace> DELETE phone_numbers[0] from user WHERE first_name = 'Mary' AND last_name = 'Rodriguez';
Teure Listenoperationen
Da eine Liste Werte nach ihrer Position speichert, besteht die Möglichkeit, dass Cassandra zum Aktualisieren oder Löschen eines bestimmten Elements in einer Liste die gesamte Liste lesen, den gewünschten Vorgang ausführen und die gesamte Liste wieder ausschreiben muss. Das kann bei einer großen Anzahl von Werten in der Liste sehr teuer werden. Aus diesem Grund bevorzugen viele Benutzer die Typen set
oder map
, vor allem dann, wenn die Möglichkeit besteht, den Inhalt der Sammlung zu aktualisieren.
map
-
Der Datentyp
map
enthält eine Sammlung von Schlüssel-Werte-Paaren. Die Schlüssel und Werte können von jedem Typ sein, außercounter
. Probieren wir das mal aus, indem wirmap
verwenden, um Informationen über Benutzeranmeldungen zu speichern. Erstelle eine Spalte, um die Zeit der Anmeldung in Sekunden zu erfassen, mittimeuuid
als Schlüssel:cqlsh:my_keyspace> ALTER TABLE user ADD login_sessions map<timeuuid, int>;
Dann kannst du ein paar Login-Sitzungen für Mary hinzufügen und die Ergebnisse sehen:
cqlsh:my_keyspace> UPDATE user SET login_sessions = { now(): 13, now(): 18} WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT login_sessions FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; login_sessions ----------------------------------------------- {839b2660-d1c0-11e9-8309-6d2c86545d91: 13, 839b2661-d1c0-11e9-8309-6d2c86545d91: 18} (1 rows)
Wir können auch auf ein einzelnes Element in der Karte verweisen, indem wir seinen Schlüssel verwenden.
Auflistungstypen sind sehr nützlich, wenn wir eine variable Anzahl von Elementen in einer einzigen Spalte speichern müssen.
Tupel
Nun könntest du beschließen, dass du die physischen Adressen deiner Nutzerinnen und Nutzer aufzeichnen musst. Du könntest einfach eine einzelne Textspalte verwenden, um diese Werte zu speichern, aber dann müsste die Anwendung die verschiedenen Komponenten der Adresse analysieren. Es wäre besser, wenn du eine Struktur definieren könntest, in der du die Adressen speicherst, um die Integrität der verschiedenen Komponenten zu gewährleisten.
Zum Glück bietet Cassandra zwei verschiedene Möglichkeiten, komplexere Datenstrukturen zu verwalten: Tupel und benutzerdefinierte Typen.
Werfen wir zunächst einen Blick auf Tupel, die eine Möglichkeit bieten, eine Menge von Werten verschiedener Typen mit fester Länge zu haben. Du könntest zum Beispiel eine Tupelspalte in die Tabelle user
einfügen, die eine Adresse speichert. Du hättest ein Tupel hinzufügen können, um Adressen zu definieren, wobei du ein dreizeiliges Adressformat und eine ganzzahlige Postleitzahl wie eine US-Postleitzahl vorausgesetzt hättest:
cqlsh:my_keyspace> ALTER TABLE user ADD address tuple<text, text, text, int>;
Dann kannst du eine Adresse mit der folgenden Anweisung ausfüllen:
cqlsh:my_keyspace> UPDATE user SET address = ('7712 E. Broadway', 'Tucson', 'AZ', 85715 ) WHERE first_name = 'Mary' AND last_name = 'Rodriguez';
Damit kannst du zwar eine Adresse speichern, aber es kann etwas umständlich sein, sich die Positionswerte der verschiedenen Felder eines Tupels zu merken, ohne dass jedem Wert ein Name zugeordnet ist. Es gibt auch keine Möglichkeit, einzelne Felder eines Tupels zu aktualisieren; das gesamte Tupel muss aktualisiert werden. Aus diesen Gründen werden Tupel in der Praxis nur selten verwendet, denn Cassandra bietet eine Alternative, die es ermöglicht, jeden Wert zu benennen und darauf zuzugreifen, was wir im Folgenden untersuchen werden.
Aber zuerst verwenden wir den CQL-Befehl DROP
, um die Spalte address
loszuwerden, damit du sie durch etwas Besseres ersetzen kannst:
cqlsh:my_keyspace> ALTER TABLE user DROP address;
Benutzerdefinierte Typen
Cassandra bietet dir die Möglichkeit, deine eigenen Typen zu definieren, um das Datenmodell zu erweitern. Diese benutzerdefinierten Typen (UDTs) sind einfacher zu verwenden als Tupel, da du die Werte nach Namen und nicht nach Position angeben kannst. Erstelle deinen eigenen Adresstyp:
cqlsh:my_keyspace> CREATE TYPE address ( street text, city text, state text, zip_code int);
Ein UDT wird durch den Schlüsselbereich, in dem es definiert ist, skaliert. Du hättest auch CREATE TYPE my_keyspace.address
schreiben können. Wenn du den Befehl DESCRIBE KEYSPACE my_keyspace
ausführst, siehst du, dass der Adresstyp Teil der Keyspace-Definition ist.
Nachdem du nun den Typ address
definiert hast, kannst du ihn in der Tabelle user
verwenden. Anstatt nur eine einzelne Adresse hinzuzufügen, kannst du eine Karte verwenden, um mehrere Adressen zu speichern, denen du Namen wie "Zuhause", "Arbeit" usw. geben kannst. Allerdings stößt man dabei sofort auf ein Problem:
cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text, address>; InvalidRequest: code=2200 [Invalid query] message="Non-frozen collections are not allowed inside collections: map<text, address>"
Was ist hier los? Es stellt sich heraus, dass ein benutzerdefinierter Datentyp als Sammlung betrachtet wird, da seine Implementierung ähnlich wie set
, list
oder map
ist. Du hast Cassandra gebeten, eine Sammlung in einer anderen zu verschachteln.
Sammlungen einfrieren
Cassandra-Versionen vor 2.2 unterstützen die Verschachtelung von Sammlungen nicht vollständig. Insbesondere der Zugriff auf einzelne Attribute einer verschachtelten Sammlung wird noch nicht unterstützt, da die verschachtelte Sammlung von der Implementierung als ein einziges Objekt serialisiert wird. Daher muss die gesamte verschachtelte Sammlung als Ganzes gelesen und geschrieben werden.
Freezing ist ein Konzept, das als Vorwärtskompatibilitätsmechanismus eingeführt wurde. Im Moment kannst du eine Sammlung innerhalb einer anderen Sammlung verschachteln, indem du sie als frozen
kennzeichnest, was bedeutet, dass Cassandra diesen Wert als Blob von Binärdaten speichert. In Zukunft, wenn verschachtelte Sammlungen vollständig unterstützt werden, wird es einen Mechanismus geben, um die verschachtelten Sammlungen "aufzutauen", so dass auf die einzelnen Attribute zugegriffen werden kann.
Du kannst auch eine Sammlung als Primärschlüssel verwenden, wenn sie eingefroren ist.
Nachdem wir nun einen kurzen Abstecher zum Einfrieren und zu verschachtelten Sammlungen gemacht haben, kommen wir zurück zur Änderung deiner Tabelle und markieren diesmal die Adresse als eingefroren:
cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text, frozen<address>>;
Fügen wir nun eine Adresse für Mary hinzu:
cqlsh:my_keyspace> UPDATE user SET addresses = addresses + {'home': { street: '7712 E. Broadway', city: 'Tucson', state: 'AZ', zip_code: 85715 } } WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; cqlsh:my_keyspace> SELECT addresses FROM user WHERE first_name = 'Mary' AND last_name = 'Rodriguez'; addresses --------------------------------------------------------- {'home': {street: '7712 E. Broadway', city: 'Tucson', state: 'AZ', zip_code: 85715}} (1 rows)
Nachdem du nun die verschiedenen Typen kennengelernt hast, gehen wir einen Schritt zurück und schauen uns die Tabellen an, die du bisher erstellt hast, indem du my_keyspace
beschreibst:
cqlsh:my_keyspace> DESCRIBE KEYSPACE my_keyspace ; CREATE KEYSPACE my_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1') AND durable_writes = true; CREATE TYPE my_keyspace.address ( street text, city text, state text, zip_code int ); CREATE TABLE my_keyspace.user ( last_name text, first_name text, addresses map<text, frozen<address>>, emails set<text>, id uuid, login_sessions map<timeuuid, int>, middle_initial text, phone_numbers list<text>, title text, PRIMARY KEY (last_name, first_name) ) WITH CLUSTERING ORDER BY (first_name ASC) AND bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction .SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 AND default_time_to_live = 0 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 AND min_index_interval = 128 AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; CREATE TABLE my_keyspace.user_visits ( user_id uuid PRIMARY KEY, visits counter ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction .SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 AND default_time_to_live = 0 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 AND min_index_interval = 128 AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE';
CQL-Befehle üben
Die Befehle, die in diesem Kapitel aufgeführt sind, um die Tabelle user
zu bearbeiten, sind als Gist auf GitHub verfügbar, damit du sie leichter ausführen kannst. Die Datei heißt cqlsh_intro.cql.
Zusammenfassung
In diesem Kapitel hast du einen kurzen Überblick über das Datenmodell von Cassandra mit Clustern, Keyspaces, Tabellen, Schlüsseln, Zeilen und Spalten erhalten. Dabei hast du viel über die CQL-Syntax gelernt und mehr Erfahrung bei der Arbeit mit Tabellen und Spalten in cqlsh
gesammelt. Wenn du tiefer in CQL einsteigen möchtest, kannst du die vollständige Sprachspezifikation lesen.
Get Cassandra: The Definitive Guide, (Revised) Third Edition, 3. now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.