Auf dieser Seite werden die Anforderungen an das Spanner-Schema, die Verwendung des Schemas zum Erstellen hierarchischer Beziehungen und die Schemafunktionen beschrieben. Außerdem werden verschränkte Tabellen eingeführt, die die Abfrageleistung verbessern können, wenn Tabellen mit einer hierarchischen Beziehung abgefragt werden.
Ein Schema ist ein Namespace, der Datenbankobjekte wie Tabellen, Ansichten, Indexe und Funktionen enthält. Sie verwenden Schemas, um Objekte zu organisieren, detaillierte Zugriffssteuerungsberechtigungen anzuwenden und Namenskonflikte zu vermeiden. Sie müssen für jede Datenbank in Spanner ein Schema definieren.
Sie können Zeilen in Ihrer Datenbanktabelle auch in verschiedenen geografischen Regionen weiter segmentieren und speichern. Weitere Informationen finden Sie in der Übersicht zur geografischen Partitionierung.
Stark typisierte Daten
Daten in Spanner sind stark typisiert. Zu den Datentypen gehören skalare und komplexe Typen, die unter Datentypen in GoogleSQL und PostgreSQL-Datentypen beschrieben werden.
Primärschlüssel auswählen
Spanner-Datenbanken können eine oder mehrere Tabellen enthalten. Tabellen sind als Zeilen und Spalten strukturiert. Im Tabellenschema wird eine oder mehrere Tabellenspalten als Primärschlüssel der Tabelle definiert, mit dem jede Zeile eindeutig identifiziert wird. Primärschlüssel werden immer indexiert, um Zeilen schnell zu finden. Wenn Sie vorhandene Zeilen in einer Tabelle aktualisieren oder löschen möchten, muss die Tabelle über einen Primärschlüssel verfügen. Eine Tabelle ohne Primärschlüsselspalten kann nur eine Zeile enthalten. Nur GoogleSQL-Dialektdatenbanken können Tabellen ohne Primärschlüssel enthalten.
Oft hat Ihre Anwendung bereits ein Feld, das als Primärschlüssel verwendet werden kann. Für eine Customers-Tabelle kann beispielsweise eine von einer Anwendung bereitgestellte CustomerId vorhanden sein, die als Primärschlüssel dient. In anderen Fällen müssen Sie eventuell beim Einfügen einer Zeile einen Primärschlüssel generieren. Das ist in der Regel ein eindeutiger Ganzzahlwert ohne geschäftliche Bedeutung (ein surrogater Primärschlüssel).
In allen Fällen sollten Sie darauf achten, keine Hotspots mit der Wahl Ihres Primärschlüssels zu erstellen. Wenn Sie beispielsweise Datensätze mit einer monoton ansteigenden Ganzzahl als Schlüssel einfügen, fügen Sie sie immer am Ende des Schlüsselbereichs ein. Dies ist nicht wünschenswert, da Spanner die Daten zwischen den Servern nach Schlüsselbereichen aufteilt. Dies bedeutet, dass Ihre Einfügungen auf einen einzelnen Server gerichtet werden und einen Hotspot bilden. Mit diesen Verfahren können Sie die Last auf mehrere Server verteilen und Hotspots vermeiden:
- Hashen Sie den Schlüssel und speichern Sie ihn in einer Spalte. Verwenden Sie die Hash-Spalte (oder die Hash-Spalte und die Spalten mit dem eindeutigen Schlüssel) als Primärschlüssel.
- Vertauschen Sie die Reihenfolge der Spalten im Primärschlüssel.
- Verwenden Sie eine UUID (Universally Unique Identifier). Wir empfehlen die Version 4 der UUID, da bei dieser Version zufällige Werte in den Bits höherer Ordnung verwendet werden. Verwenden Sie keinen UUID-Algorithmus (beispielsweise Version 1 der UUID), bei dem der Zeitstempel in den Bits höherer Ordnung gespeichert wird.
- Bit-Umkehrungen für sequenzielle Werte
Hierarchische Tabellenbeziehungen
Es gibt zwei Möglichkeiten, hierarchische Beziehungen in Spanner zu definieren: Tabellenverschränkung und Fremdschlüssel.
Die Tabellenverschachtelung von Spanner ist eine gute Wahl für viele hierarchische Beziehungen. Beim Verschränken werden untergeordnete Zeilen physisch zusammen mit übergeordneten Zeilen im Speicher platziert. Das Speichern von untergeordneten und übergeordneten Zeilen an einem Ort kann die Leistung erheblich verbessern. Wenn Sie beispielsweise eine Tabelle Customers und eine Tabelle Invoices haben und Ihre Anwendung ruft häufig alle Rechnungen für einen Kunden ab, können Sie Invoices als verschränkte untergeordnete Tabelle von Customers definieren. Dazu deklarieren Sie eine Datenlokalitätsbeziehung zwischen zwei unabhängigen Tabellen. Sie weisen Spanner an, eine oder mehrere Zeilen von Invoices mit einer Customers-Zeile zu speichern. Diese Über-/Untergeordnet-Beziehung wird bei der Verschränkung mit der INTERLEAVE IN PARENT-Klausel erzwungen. INTERLEAVE IN-Untergeordnete Tabellen haben dieselben physischen Zeilenverschachtelungsmerkmale, aber Spanner erzwingt keine referenzielle Integrität zwischen übergeordneten und untergeordneten Tabellen.
Sie verknüpfen eine untergeordnete Tabelle mit einer übergeordneten Tabelle, indem Sie DDL verwenden, mit der die untergeordnete Tabelle als in der übergeordneten Tabelle verschränkt deklariert wird, und indem Sie den Primärschlüssel der übergeordneten Tabelle als ersten Teil des zusammengesetzten Primärschlüssels der untergeordneten Tabelle einfügen.
Weitere Informationen zur Verschränkung von Tabellen finden Sie unter Verschränkte Tabellen erstellen.
Fremdschlüssel sind eine allgemeinere über-/untergeordnete Lösung, die zusätzliche Anwendungsfälle behandelt. Sie sind nicht auf Primärschlüsselspalten beschränkt und Tabellen können mehrere Fremdschlüsselbeziehungen haben, die in einigen Beziehungen als übergeordnete und in anderen als untergeordnete Beziehungen gelten. Eine Fremdschlüsselbeziehung impliziert jedoch nicht, dass sich die Tabellen auf der Speicherebene befinden.
Google empfiehlt, dass Sie hierarchische Beziehungen entweder als verschränkte Tabellen oder als Fremdschlüssel darstellen, aber nicht beides. Weitere Informationen zu Fremdschlüsseln und ihrem Vergleich mit verschränkten Tabellen finden Sie in der Übersicht über Fremdschlüssel.
Primärschlüssel in verschränkten Tabellen
Für das Interleaving muss jede Tabelle einen Primärschlüssel haben. Wenn Sie eine Tabelle als verschachtelte untergeordnete Tabelle einer anderen Tabelle deklarieren, muss die Tabelle einen zusammengesetzten Primärschlüssel haben, der alle Komponenten des Primärschlüssels der übergeordneten Tabelle in derselben Reihenfolge und in der Regel eine oder mehrere zusätzliche Spalten der untergeordneten Tabelle enthält.
Cloud Spanner speichert Zeilen in einer Reihenfolge nach Primärschlüsselwerten, wobei untergeordnete Zeilen zwischen übergeordneten Zeilen eingefügt werden. Eine Abbildung verschränkter Zeilen finden Sie im Abschnitt Verschränkte Tabellen erstellen weiter unten auf dieser Seite.
Zusammenfassung: Mit Spanner können Sie Zeilen relationaler Tabellen physisch gemeinsam speichern. Die Schemabeispiele zeigen, wie dieses physische Layout aussieht.
Datenbank-Splits
Sie können Hierarchien von verschränkten hierarchischen Beziehungen mit bis zu sieben Ebenen definieren, was bedeutet, dass Sie Zeilen mit sieben unabhängigen Tabellen zusammen unterbringen können. Wenn die Größe der Daten in Ihren Tabellen gering ist, kann Ihre Datenbank wahrscheinlich von einem einzigen Spanner-Server verarbeitet werden. Aber was passiert, wenn Ihre relationalen Tabellen wachsen und die Ressourcengrenzen eines einzelnen Servers erreichen? Spanner ist eine verteilte Datenbank. Wenn Ihre Datenbank wächst, teilt Spanner Ihre Daten in Abschnitte auf, die als „Splits“ bezeichnet werden. Aufteilungen können sich unabhängig voneinander bewegen und verschiedenen Servern zugewiesen werden, die sich an verschiedenen physischen Standorten befinden können. Ein Split enthält einen Bereich von zusammenhängenden Zeilen. Die Start- und Endschlüssel dieses Bereichs werden als „Split-Grenzen“ bezeichnet. Spanner fügt automatisch Split-Grenzen entsprechend der Größe und Last hinzu und entfernt sie. Dadurch verändert sich die Anzahl der Splits in der Datenbank.
Lastbasierte Aufteilung
Als Beispiel dafür, wie Spanner die lastbasierte Aufteilung zur Vermeidung von Hotspots vornimmt, gehen Sie erst einmal davon aus, dass Ihre Datenbank eine Tabelle mit 10 Zeilen enthält, die öfter gelesen werden als alle anderen Zeilen in der Tabelle. Spanner kann Split-Grenzen zwischen jeder dieser 10 Zeilen einfügen, sodass sie alle von einem anderen Server verarbeitet werden, anstatt dass alle Lesevorgänge dieser Zeilen die Ressourcen eines einzelnen Servers verbrauchen können.
Wenn Sie die Best Practices für das Schemadesign befolgen, kann Spanner Hotspots in der Regel so reduzieren, dass sich der Lesedurchsatz alle paar Minuten verbessert, bis die Ressourcen in Ihrer Instanz belegt sind oder Fälle auftreten, in denen keine neuen Split-Grenzen hinzugefügt werden können (weil Sie einen Split haben, der nur eine einzelne Zeile ohne verschachtelte untergeordnete Elemente umfasst).
Benannte Schemas
Mit benannten Schemas können Sie ähnliche Daten zusammen organisieren. So können Sie Objekte in der Google Cloud Konsole schnell finden, Berechtigungen anwenden und Namenskonflikte vermeiden.
Benannte Schemas werden wie andere Datenbankobjekte mit DDL verwaltet.
Mit benannten Schemas in Spanner können Sie Daten mit voll qualifizierten Namen (Fully Qualified Names, FQNs) abfragen. Mit vollständig qualifizierten Namen können Sie den Schemanamen und den Objektnamen kombinieren, um Datenbankobjekte zu identifizieren. Sie könnten beispielsweise ein Schema mit dem Namen warehouse für die Geschäftseinheit „Lager“ erstellen. Die Tabellen, die dieses Schema verwenden, könnten product, order und customer information enthalten. Alternativ können Sie ein Schema mit dem Namen fulfillment für die Geschäftseinheit für die Auftragsabwicklung erstellen.
Dieses Schema könnte auch Tabellen mit den Namen product, order und customer
information enthalten. Im ersten Beispiel ist der FQN warehouse.product und im zweiten Beispiel fulfillment.product. So wird Verwirrung in Situationen vermieden, in denen mehrere Objekte denselben Namen haben.
In der CREATE SCHEMA-DDL haben Tabellenobjekte sowohl einen FQN, z. B. sales.customers, als auch einen Kurznamen, z. B. sales.
Die folgenden Datenbankobjekte unterstützen benannte Schemas:
TABLECREATEINTERLEAVE IN [PARENT]FOREIGN KEYSYNONYM
VIEWINDEXFOREIGN KEYSEQUENCE
Weitere Informationen zur Verwendung benannter Schemas finden Sie unter Benannte Schemas verwalten.
Detaillierte Zugriffssteuerung mit benannten Schemas verwenden
Mit benannten Schemas können Sie den Zugriff auf Schemaebene für jedes Objekt im Schema gewähren. Dies gilt für Schemaobjekte, die zum Zeitpunkt der Zugriffsgewährung vorhanden sind. Sie müssen Zugriff auf Objekte gewähren, die später hinzugefügt werden.
Die detaillierte Zugriffssteuerung beschränkt den Zugriff auf ganze Gruppen von Datenbankobjekten wie Tabellen, Spalten und Zeilen in der Tabelle.
Weitere Informationen finden Sie unter Detaillierte Zugriffssteuerungsberechtigungen für benannte Schemas gewähren.
Schemabeispiele
Die Schemabeispiele in diesem Abschnitt zeigen, wie Sie übergeordnete und untergeordnete Tabellen mit und ohne Verschränkung erstellen, und veranschaulichen die entsprechenden physischen Layouts der Daten.
Übergeordnete Tabelle erstellen
Angenommen, Sie erstellen eine Musikanwendung und Sie benötigen eine Tabelle, in der Zeilen von Sängerdaten gespeichert werden:
Beachten Sie, dass die Tabelle eine Primärschlüsselspalte SingerId enthält, die links von der fett gedruckten Linie angezeigt wird, und dass die Tabellen nach Zeilen und Spalten geordnet sind.
Sie können die Tabelle mit der folgenden DDL definieren:
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), SingerInfo BYTES(MAX), );
PostgreSQL
CREATE TABLE singers ( singer_id BIGINT PRIMARY KEY, first_name VARCHAR(1024), last_name VARCHAR(1024), singer_info BYTEA );
Beachten Sie Folgendes zum Beispielschema:
Singersist eine Tabelle im Stammverzeichnis der Datenbankhierarchie, da sie nicht als verschränktes untergeordnetes Element einer anderen Tabelle definiert ist.- Bei Datenbanken mit GoogleSQL-Dialekt werden Primärschlüsselspalten in der Regel mit
NOT NULLannotiert. Sie können diese Annotation jedoch weglassen, wenn SieNULL-Werte in Schlüsselspalten zulassen möchten. Weitere Informationen finden Sie unter Schlüsselspalten. - Nicht im Primärschlüssel enthaltene Spalten werden als Nicht-Schlüsselspalten bezeichnet und können die optionale Annotation
NOT NULLenthalten. - Spalten, die den Typ
STRINGoderBYTESin GoogleSQL verwenden, müssen mit einer Länge definiert werden, die die maximale Anzahl von Unicode-Zeichen angibt, die im Feld gespeichert werden können. Die Längenspezifikation ist für die PostgreSQL-Typenvarcharundcharacter varyingoptional. Weitere Informationen finden Sie unter Skalare Datentypen für Datenbanken mit GoogleSQL-Dialekt und PostgreSQL-Datentypen für Datenbanken mit PostgreSQL-Dialekt.
Wie sieht das physische Layout der Zeilen in der Tabelle Singers aus? Das folgende Diagramm zeigt die Zeilen der Tabelle Singers, die nach Primärschlüssel gespeichert sind („Singers(1)“ und dann „Singers(2)“, wobei die Zahl in Klammern der Primärschlüsselwert ist).
Das obige Diagramm zeigt eine Beispiel-Split-Grenze zwischen den Zeilen, die mit Singers(3) und Singers(4) codiert sind. Dabei sind die Daten aus den entstandenen Splits verschiedenen Servern zugewiesen. Wenn diese Tabelle wächst, können Zeilen mit Singers-Daten an verschiedenen Standorten gespeichert werden.
Übergeordnete und untergeordnete Tabellen erstellen
Angenommen, Sie möchten jetzt in der Musikanwendung einige grundlegende Daten zum Album jedes Sängers hinzufügen.
Beachten Sie, dass der Primärschlüssel von Albums aus zwei Spalten besteht: SingerId und AlbumId, um jedes Album mit seinem Sänger zu verknüpfen. Das im Folgenden aufgeführte Beispielschema definiert sowohl die Albums- als auch die Singers-Tabelle im Stamm der Datenbankhierarchie. Damit sind diese Tabellen hierarchisch gleichgeordnet.
-- Schema hierarchy: -- + Singers (sibling table of Albums) -- + Albums (sibling table of Singers)
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), SingerInfo BYTES(MAX), ); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId);
PostgreSQL
CREATE TABLE singers ( singer_id BIGINT PRIMARY KEY, first_name VARCHAR(1024), last_name VARCHAR(1024), singer_info BYTEA ); CREATE TABLE albums ( singer_id BIGINT, album_id BIGINT, album_title VARCHAR, PRIMARY KEY (singer_id, album_id) );
Das physische Layout der Zeilen von Singers und Albums gleicht dem des folgenden Diagramms. Die Zeilen der Tabelle Albums sind nach zusammenhängenden Primärschlüsseln gespeichert. Danach sind die Zeilen der Tabelle Singers nach zusammenhängenden Primärschlüsseln gespeichert:
Wichtiger Hinweis zum Schema: Spanner geht von keiner Datenlokalitätsbeziehung zwischen den Tabellen Singers und Albums aus, da es sich um übergeordnete Tabellen handelt. Wenn die Datenbank wächst, kann Spanner Split-Grenzen zwischen allen Zeilen einfügen. Dies bedeutet, dass sich die Zeilen der Tabelle Albums am Ende in einem anderen Split als die Zeilen der Tabelle Singers befinden können und die beiden Splits sich unabhängig voneinander verschieben können.
Je nach den Anforderungen Ihrer Anwendung kann es sinnvoll sein, dass Albums-Daten auf verschiedenen Splits von Singers-Daten gespeichert werden. Dies kann jedoch zu Leistungseinbußen führen, da Lese- und Aktualisierungsvorgänge für verschiedene Ressourcen koordiniert werden müssen. Wenn Ihre Anwendung häufig Informationen zu allen Alben eines bestimmten Sängers abrufen muss, sollten Sie Albums als verschränkte untergeordnete Tabelle von Singers erstellen, damit Zeilen aus beiden Tabellen unter der Primärschlüsseldimension gemeinsam gespeichert sind. Im nächsten Beispiel wird dies genauer erläutert.
Verschränkte Tabellen erstellen
Eine verschränkte Tabelle ist eine Tabelle, die Sie als verschränktes untergeordnetes Element einer anderen Tabelle deklarieren, weil die Zeilen der untergeordneten Tabelle zusammen mit der zugehörigen übergeordneten Zeile gespeichert werden sollen. Wie bereits erwähnt, muss der Primärschlüssel der übergeordneten Tabelle der erste Teil des zusammengesetzten Primärschlüssels der untergeordneten Tabelle sein.
Nachdem Sie eine Tabelle verschachtelt haben, kann dies nicht mehr rückgängig gemacht werden. Das Interleaving kann nicht rückgängig gemacht werden. Stattdessen müssen Sie die Tabelle neu erstellen und Daten dorthin migrieren.
Angenommen, Sie entwickeln eine Musik-App und stellen fest, dass die App häufig auf Zeilen aus der Tabelle Albums zugreifen muss, wenn sie auf eine Zeile Singers zugreift. Wenn Sie beispielsweise auf die Zeile Singers(1) zugreifen, müssen Sie auch auf die Zeilen Albums(1, 1) und Albums(1, 2) zugreifen. In diesem Fall müssen Singers und Albums eine starke Datenlokalitätsbeziehung aufweisen. Sie können diese Datenlokalitätsbeziehung durch Erstellen der Tabelle Albums als verschränkte untergeordnete Tabelle von Singers angeben.
-- Schema hierarchy: -- + Singers -- + Albums (interleaved table, child table of Singers)
Die fett dargestellte Zeile im folgenden Schema zeigt, wie Albums als verschränkte Tabelle von Singers erstellt wird.
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), SingerInfo BYTES(MAX), ); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId), INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
PostgreSQL
CREATE TABLE singers ( singer_id BIGINT PRIMARY KEY, first_name VARCHAR(1024), last_name VARCHAR(1024), singer_info BYTEA ); CREATE TABLE albums ( singer_id BIGINT, album_id BIGINT, album_title VARCHAR, PRIMARY KEY (singer_id, album_id) ) INTERLEAVE IN PARENT singers ON DELETE CASCADE;
Hinweise zu diesem Schema:
SingerId, der erste Teil des Primärschlüssels der untergeordneten TabelleAlbums, ist auch der Primärschlüssel der übergeordneten TabelleSingers.- Die Annotation
ON DELETE CASCADEbedeutet, dass beim Löschen einer Zeile der übergeordneten Tabelle die Zeilen der entsprechenden untergeordneten Tabelle automatisch ebenfalls gelöscht werden. Wenn eine untergeordnete Tabelle diese Annotation nicht enthält oder die AnnotationON DELETE NO ACTIONlautet, müssen Sie die untergeordneten Zeilen löschen, bevor Sie die übergeordnete Zeile löschen können. - Verschränkte Zeilen werden zuerst nach Zeilen der übergeordneten Tabelle und dann nach zusammenhängenden Zeilen der untergeordneten Tabelle mit dem Primärschlüssel des übergeordneten Elements sortiert. Zum Beispiel „Singers(1)“, dann „Albums(1, 1)“ und dann „Albums(1, 2)“.
- Die Datenlokalitätsbeziehung zwischen jedem Sänger und dessen Albumdaten wird beibehalten, wenn diese Datenbank aufgeteilt wird, sofern die Größe einer
Singers-Zeile und aller ihrerAlbums-Zeilen unter dem Limit für die Split-Größe bleibt und es keinen Hotspot in einer dieserAlbums-Zeilen gibt. - Nur wenn eine übergeordnete Zeile existiert, können Sie untergeordnete Zeilen einfügen. Die übergeordnete Zeile kann entweder bereits in der Datenbank vorhanden sein oder vor dem Einfügen der untergeordneten Zeilen in derselben Transaktion eingefügt werden.
Angenommen, Sie möchten Projects und Resources als verschachtelte Tabellen modellieren. In bestimmten Szenarien kann INTERLEAVE IN nützlich sein, da die Projects-Zeile nicht vorhanden sein muss, damit die zugehörigen Entitäten vorhanden sind. Beispiel: Ein Projekt wurde gelöscht, aber die zugehörigen Ressourcen müssen vor dem Löschen bereinigt werden.
GoogleSQL
CREATE TABLE Projects ( ProjectId INT64 NOT NULL, ProjectName STRING(1024), ) PRIMARY KEY (ProjectId); CREATE TABLE Resources ( ProjectId INT64 NOT NULL, ResourceId INT64 NOT NULL, ResourceName STRING(1024), ) PRIMARY KEY (ProjectId, ResourceId), INTERLEAVE IN Projects;
PostgreSQL
CREATE TABLE Projects ( ProjectId BIGINT PRIMARY KEY, ProjectName VARCHAR(1024), ); CREATE TABLE Resources ( ProjectId BIGINT, ResourceId BIGINT, ResourceName VARCHAR(1024), PRIMARY KEY (ProjectId, ResourceId) ) INTERLEAVE IN Projects;
In diesem Beispiel verwenden wir die INTERLEAVE IN Projects-Klausel anstelle von INTERLEAVE IN PARENT Projects. Das bedeutet, dass wir die Über-/Unterordnungsbeziehung zwischen Projekten und Ressourcen nicht erzwingen.
In diesem Beispiel können die Zeilen Resources(1, 10) und Resources(1, 20) in der Datenbank vorhanden sein, auch wenn die Zeile Projects(1) nicht vorhanden ist. Projects(1) kann auch dann gelöscht werden, wenn Resources(1, 10) und Resources(1, 20) noch vorhanden sind. Das Löschen hat keine Auswirkungen auf diese Resources-Zeilen.
Eine Hierarchie von verschränkten Tabellen erstellen
Die hierarchische Beziehung zwischen Singers und Albums kann auf weitere nachfolgende Tabellen erweitert werden. Beispielsweise können Sie eine verschränkte Tabelle namens Songs als untergeordnetes Element von Albums erstellen, um die Trackliste jedes Albums zu speichern:
Songs muss einen Primärschlüssel haben, der alle Primärschlüssel der Tabellen enthält, die in der Hierarchie auf einer höheren Ebene liegen, also SingerId und AlbumId.
-- Schema hierarchy: -- + Singers -- + Albums (interleaved table, child table of Singers) -- + Songs (interleaved table, child table of Albums)
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), SingerInfo BYTES(MAX), ); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId), INTERLEAVE IN PARENT Singers ON DELETE CASCADE; CREATE TABLE Songs ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, TrackId INT64 NOT NULL, SongName STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId, TrackId), INTERLEAVE IN PARENT Albums ON DELETE CASCADE;
PostgreSQL
CREATE TABLE singers ( singer_id BIGINT PRIMARY KEY, first_name VARCHAR(1024), last_name VARCHAR(1024), singer_info BYTEA ); CREATE TABLE albums ( singer_id BIGINT, album_id BIGINT, album_title VARCHAR, PRIMARY KEY (singer_id, album_id) ) INTERLEAVE IN PARENT singers ON DELETE CASCADE; CREATE TABLE songs ( singer_id BIGINT, album_id BIGINT, track_id BIGINT, song_name VARCHAR, PRIMARY KEY (singer_id, album_id, track_id) ) INTERLEAVE IN PARENT albums ON DELETE CASCADE;
Das folgende Diagramm zeigt eine physische Ansicht der verschränkten Zeilen.
In diesem Beispiel fügt Spanner bei zunehmender Anzahl von Sängern Split-Grenzen zwischen den Sängern hinzu, um die Datenlokalität zwischen einem Sänger und seinen Album- und Songdaten beizubehalten. Wenn die Größe einer Sängerzeile und ihrer untergeordneten Zeilen jedoch das Limit für die Split-Größe überschreitet oder in den untergeordneten Zeilen ein Hotspot erkannt wird, versucht Spanner, Split-Grenzen hinzuzufügen, um diese Hotspot-Zeile zusammen mit allen untergeordneten Zeilen zu isolieren.
Zusammenfassung: Eine übergeordnete Tabelle bildet zusammen mit allen untergeordneten und nachfolgenden Tabellen im Schema eine Tabellenhierarchie. Obwohl jede Tabelle in der Hierarchie logisch unabhängig ist, können Sie durch diese physische Verschränkung die Leistung verbessern, da die Tabellen schon vorab verknüpft werden. Dadurch können Sie auf die Zeilen der relationalen Tabellen zusammen zugreifen und gleichzeitig die Speicherzugriffe minimieren.
Joins mit verschränkten Tabellen
Verbinden Sie nach Möglichkeit Daten in verschränkten Tabellen mit dem Primärschlüssel. Da jede verschränkte Zeile normalerweise physisch in demselben Split wie die entsprechende übergeordnete Zeile gespeichert ist, kann Spanner lokale Zusammenführungen mit dem Primärschlüssel durchführen und so den Zugriff auf den Speicher und den Netzwerkverkehr minimieren. Im folgenden Beispiel werden Singers und Albums zum Primärschlüssel SingerId zusammengeführt.
GoogleSQL
SELECT s.FirstName, a.AlbumTitle FROM Singers AS s JOIN Albums AS a ON s.SingerId = a.SingerId;
PostgreSQL
SELECT s.first_name, a.album_title FROM singers AS s JOIN albums AS a ON s.singer_id = a.singer_id;
Standortgruppen
In Spanner werden Lokality-Gruppen verwendet, um die Beziehungen zwischen Tabellenspalten beizubehalten. Wenn Sie keine expliziten Standortgruppen für Ihre Tabellen erstellen, gruppiert Spanner alle Spalten in der Standortgruppe default und speichert die Daten aller Tabellen auf SSD-Speicher. Sie können Ortsgruppen für Folgendes verwenden:
Verwenden Sie Tiered Storage. Tiered Storage ist eine vollständig verwaltete Speicherfunktion, mit der Sie auswählen können, ob Ihre Daten auf Solid-State-Laufwerken (SSDs) oder Festplattenlaufwerken (HDDs) gespeichert werden sollen. Standardmäßig werden alle Daten in Spanner auf SSD-Speicher gespeichert, ohne dass abgestufter Speicher verwendet wird.
Mit der Spaltengruppierung können Sie angegebene Spalten getrennt von anderen Spalten speichern. Da die Daten für die angegebenen Spalten separat gespeichert werden, ist das Lesen von Daten aus diesen Spalten schneller als wenn alle Daten zusammengefasst werden. Wenn Sie die Spaltengruppierung verwenden möchten, müssen Sie eine Lokalisierungsgruppe erstellen, ohne Optionen für den mehrstufigen Speicher anzugeben. In Spanner werden Lokalitätsgruppen verwendet, um die angegebenen Spalten separat zu speichern. Wenn angegeben, übernehmen die Spalten ihre Richtlinie für den mehrstufigen Speicher von der Tabelle oder der Standardgruppe für die Datenlokalität. Verwenden Sie dann die DDL-Anweisung
CREATE TABLE, um eine Lokalitätsgruppe für die angegebenen Spalten festzulegen, oder die DDL-AnweisungALTER TABLE, um die von einer Tabellenspalte verwendete Lokalitätsgruppe zu ändern. Mit der DDL-Anweisung werden die Spalten festgelegt, die in der Lokalitätsgruppe gespeichert werden. Schließlich können Sie Daten in diesen Spalten effizienter lesen.
Schlüsselspalten
Dieser Abschnitt enthält einige Hinweise zu wichtigen Spalten.
Tabellenschlüssel ändern
An den Schlüsseln einer Tabelle sind keine Änderungen möglich: Sie können weder eine Schlüsselspalte zu einer vorhandenen Tabelle hinzufügen noch eine Schlüsselspalte aus einer vorhandenen Tabelle entfernen.
NULL-Werte in einem Primärschlüssel speichern
Wenn Sie in GoogleSQL NULL in einer Primärschlüsselspalte speichern möchten, lassen Sie die NOT NULL-Klausel für diese Spalte im Schema weg. (Datenbanken mit PostgreSQL-Dialekt unterstützen keine NULL-Werte in einer Primärschlüsselspalte.)
Das folgende Beispiel zeigt das Weglassen der NOT NULL-Klausel in der Primärschlüsselspalte SingerId. Da SingerId der Primärschlüssel ist, kann es nur eine Zeile geben, in der NULL in dieser Spalte gespeichert ist.
CREATE TABLE Singers ( SingerId INT64 PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), );
Die Eigenschaft der Primärschlüsselspalte, für die Nullwerte zulässig sind, muss zwischen der übergeordneten und der untergeordneten Tabellendeklaration übereinstimmen. In diesem Beispiel ist NOT NULL für die Spalte Albums.SingerId nicht zulässig, da Singers.SingerId sie auslässt.
CREATE TABLE Singers ( SingerId INT64 PRIMARY KEY, FirstName STRING(1024), LastName STRING(1024), ); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId), INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
Unzulässige Typen
Die folgenden Spalten können nicht vom Typ ARRAY sein:
- Die Schlüsselspalten einer Tabelle
- Die Schlüsselspalten eines Index
Für Mehrmandantenfähigkeit entwickeln
Falls Sie Daten speichern, die verschiedenen Kunden gehören, möchten Sie eventuell für Mehrinstanzenfähigkeit sorgen. Zum Beispiel kann es für einen Musikdienst von Interesse sein, die Inhalte jedes einzelnen Plattenlabels separat zu speichern.
Klassische Mehrinstanzenfähigkeit
Der gängige Ansatz, für Mehrinstanzenfähigkeit zu sorgen, besteht darin, für jeden Kunden eine eigene Datenbank zu erstellen. In diesem Beispiel hat jede Datenbank ihre eigene Tabelle Singers:
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | Marc | Richards |
| 2 | Catalina | Smith |
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | Alice | Trentor |
| 2 | Gabriel | Wright |
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | Benjamin | Martinez |
| 2 | Hanna | Harris |
Schemaverwaltete Mandantenfähigkeit
Eine weitere Möglichkeit, die Mehrmandantenfähigkeit in Spanner zu gewährleisten, besteht darin, alle Kunden in einer einzelnen Tabelle in einer einzelnen Datenbank zu speichern und für jeden Kunden einen anderen Primärschlüsselwert zu verwenden. Sie können beispielsweise eine Spalte mit dem Schlüssel CustomerId in Ihre Tabellen einfügen. Wenn Sie CustomerId zur ersten Schlüsselspalte erklären, ist die Lokalität der Daten für jeden Kunden passend. Spanner kann dann effektiv Datenbank-Splits verwenden, um die Leistung anhand von Datengröße und Lademustern zu maximieren. Im folgenden Beispiel gibt es eine einzige Singers-Tabelle für alle Kunden:
| CustomerId | SingerId | FirstName | LastName |
|---|---|---|---|
| 1 | 1 | Marc | Richards |
| 1 | 2 | Catalina | Smith |
| 2 | 1 | Alice | Trentor |
| 2 | 2 | Gabriel | Wright |
| 3 | 1 | Benjamin | Martinez |
| 3 | 2 | Hanna | Harris |
Wenn für jeden Kunden eine separate Datenbank erforderlich ist, sind diese Einschränkungen zu beachten:
- Es gelten Beschränkungen für die Anzahl der Datenbanken pro Instanz und für die Anzahl der Tabellen und Indexe pro Datenbank. Je nach Kundenanzahl ist es gegebenenfalls nicht möglich, separate Datenbanken oder Tabellen zu verwenden.
- Das Hinzufügen neuer Tabellen und nicht verschränkter Indexe kann sehr lange dauern. Wenn Ihre Schemas so gestaltet sind, dass das Einfügen neuer Tabellen und Indexe erforderlich ist, können Sie möglicherweise nicht die gewünschte Leistung erzielen.
Das Erstellen separater Datenbanken funktioniert eventuell besser, wenn Sie Ihre Tabellen so auf die Datenbanken verteilen, dass jede Datenbank eine geringe Anzahl von Schemaänderungen pro Woche aufweist.
Wenn Sie bei Ihrer Anwendung für jeden Kunden separate Tabellen und Indexe erstellen, sollten Sie nicht alle Tabellen und Indexe in derselben Datenbank ablegen. Teilen Sie sie stattdessen auf viele Datenbanken auf, um Leistungsprobleme beim Erstellen einer großen Anzahl von Indexen zu minimieren.
Weitere Informationen zu anderen Datenverwaltungsmustern und zum Anwendungsdesign für Mehrmandantenfähigkeit finden Sie unter Mehrmandantenfähigkeit in Spanner implementieren.