Auf dieser Seite wird beschrieben, wie Sie Suchindexe hinzufügen und verwenden. Die Volltextsuche wird für Einträge im Suchindex ausgeführt.
Suchindexe verwenden
Sie können einen Suchindex für alle Spalten erstellen, die für Volltextsuchen verfügbar sein sollen. Verwenden Sie zum Erstellen eines Suchindex die DDL-Anweisung CREATE SEARCH INDEX
. Verwenden Sie zum Aktualisieren eines Index die DDL-Anweisung ALTER SEARCH INDEX
. Spanner erstellt und verwaltet den Suchindex automatisch. Dazu gehört auch das Hinzufügen und Aktualisieren von Daten im Suchindex, sobald sich die Daten in der Datenbank ändern.
Suchindexpartitionen
Ein Suchindex kann partitioniert oder nicht partitioniert sein, je nachdem, welche Art von Abfragen Sie beschleunigen möchten.
Ein Beispiel dafür, wann ein partitionierter Index die beste Wahl ist, ist, wenn die Anwendung ein E-Mail-Postfach abfragt. Jede Anfrage ist auf ein bestimmtes Postfach beschränkt.
Ein Beispiel dafür, wann eine nicht partitionierte Abfrage die beste Wahl ist, ist eine Abfrage für alle Produktkategorien in einem Produktkatalog.
Anwendungsfälle für den Suchindex
Neben der Volltextsuche unterstützen Spanner-Suchindexe Folgendes:
- JSON-Suchvorgänge sind eine effiziente Methode zum Indexieren und Abfragen von JSON- und JSONB-Dokumenten.
- Teilstring-Suchanfragen sind Suchanfragen, bei denen in einem längeren Text nach einer kürzeren Zeichenfolge (dem Teilstring) gesucht wird.
- Kombinieren von Bedingungen für eine beliebige Teilmenge indexierter Daten, einschließlich Exact Match und numerischer Daten, in einem einzelnen Indexscan.
Weitere Informationen zu Anwendungsfällen finden Sie unter Suche im Vergleich zu sekundären Indexen.
Beispiel für einen Suchindex
Um die Möglichkeiten von Suchindexen zu veranschaulichen, nehmen wir an, dass es eine Tabelle gibt, in der Informationen zu Musikalben gespeichert sind:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX)
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
albumtitle character varying,
PRIMARY KEY(albumid));
Spanner bietet mehrere Tokenisierungsfunktionen, mit denen Tokens erstellt werden. Wenn Sie die vorherige Tabelle so ändern möchten, dass Nutzer eine Volltextsuche nach Albumtiteln durchführen können, verwenden Sie die Funktion TOKENIZE_FULLTEXT
, um Tokens aus Albumtiteln zu erstellen. Erstellen Sie dann eine Spalte mit dem Datentyp TOKENLIST
, in der die Tokenisierungsausgabe von TOKENIZE_FULLTEXT
gespeichert wird.
In diesem Beispiel erstellen wir die Spalte AlbumTitle_Tokens
.
GoogleSQL
ALTER TABLE Albums
ADD COLUMN AlbumTitle_Tokens TOKENLIST
AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN;
PostgreSQL
ALTER TABLE albums
ADD COLUMN albumtitle_tokens spanner.tokenlist
GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN;
Im folgenden Beispiel wird die DDL CREATE SEARCH INDEX
verwendet, um einen Suchindex (AlbumsIndex
) für die AlbumTitle
-Tokens (AlbumTitle_Tokens
) zu erstellen:
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens);
PostgreSQL
In diesem Beispiel wird CREATE SEARCH INDEX
verwendet.
CREATE SEARCH INDEX albumsindex ON albums(albumtitle_tokens);
Nachdem Sie den Suchindex hinzugefügt haben, können Sie mit SQL-Abfragen nach Alben suchen, die den Suchkriterien entsprechen. Beispiel:
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
Datenkonsistenz
Wenn ein Index erstellt wird, verwendet Spanner automatisierte Prozesse, um die Daten zu sichern und so die Konsistenz zu gewährleisten. Wenn Schreibvorgänge committet werden, werden die Indexe in derselben Transaktion aktualisiert. Spanner führt automatisch Prüfungen der Datenkonsistenz durch.
Schemadefinitionen für Suchindexe
Suchindexe werden für eine oder mehrere TOKENLIST
-Spalten einer Tabelle definiert. Suchindexe bestehen aus den folgenden Komponenten:
- Basistabelle: Die Spanner-Tabelle, die indexiert werden muss.
- Spalte
TOKENLIST
: Eine Sammlung von Spalten, die die zu indexierenden Tokens definieren. Die Reihenfolge dieser Spalten ist unwichtig.
In der folgenden Anweisung ist die Basistabelle beispielsweise „Albums“. TOKENLIST
-Spalten werden für AlbumTitle
(AlbumTitle_Tokens
) und Rating
(Rating_Tokens
) erstellt.
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
SingerId INT64 NOT NULL,
ReleaseTimestamp INT64 NOT NULL,
AlbumTitle STRING(MAX),
Rating FLOAT64,
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
Rating_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Rating)) HIDDEN
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
singerid bigint NOT NULL,
releasetimestamp bigint NOT NULL,
albumtitle character varying,
rating double precision,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,
rating_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(rating)) VIRTUAL HIDDEN,
PRIMARY KEY(AlbumId));
Verwenden Sie die folgende CREATE SEARCH INDEX
-Anweisung, um einen Suchindex mit den Tokens für AlbumTitle
und Rating
zu erstellen:
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
PostgreSQL
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
Für Suchindexe sind die folgenden Optionen verfügbar:
- Partitionen: Eine optionale Gruppe von Spalten, mit denen der Suchindex unterteilt wird. Das Abfragen eines partitionierten Index ist oft deutlich effizienter als das Abfragen eines nicht partitionierten Index. Weitere Informationen finden Sie unter Suchindexe partitionieren.
- Spalte für Sortierreihenfolge: Eine optionale
INT64
-Spalte, in der die Reihenfolge für den Abruf aus dem Suchindex festgelegt wird. Weitere Informationen finden Sie unter Sortierreihenfolge für Suchindex. - Verschränkung: Wie bei sekundären Indexen können Sie Suchindexe verschränken. Für verschachtelte Suchindexe sind weniger Ressourcen zum Schreiben und Verknüpfen mit der Basistabelle erforderlich. Weitere Informationen finden Sie unter Verschachtelte Suchindexe.
- Options-Klausel: Eine Liste von Schlüssel/Wert-Paaren, die die Standardeinstellungen des Suchindex überschreibt.
Internes Layout von Suchindexen
Ein wichtiges Element der internen Darstellung von Suchindexen ist eine docid, die als speichereffiziente Darstellung des Primärschlüssels der Basistabelle dient, der beliebig lang sein kann. Außerdem wird dadurch die Reihenfolge für das interne Datenlayout gemäß den vom Nutzer angegebenen ORDER BY
-Spalten der CREATE SEARCH INDEX
-Anweisung festgelegt. Sie wird als eine oder zwei 64-Bit-Ganzzahlen dargestellt.
Suchindexe werden intern als zweistufige Zuordnung implementiert:
- Tokens zu Dokument-IDs
- Docids zu Primärschlüsseln der Basistabelle
Dieses Schema führt zu erheblichen Speicherplatzeinsparungen, da Spanner den vollständigen Primärschlüssel der Basistabelle nicht für jedes <token, document>
-Paar speichern muss.
Es gibt zwei Arten von physischen Indexen, die die beiden Ebenen der Zuordnung implementieren:
- Ein Sekundärindex, der Partitionsschlüssel und eine „docid“ dem Primärschlüssel der Basistabelle zuordnet. Im Beispiel im vorherigen Abschnitt wird
{SingerId, ReleaseTimestamp, uid}
zu{AlbumId}
zugeordnet. Im sekundären Index werden auch alle Spalten gespeichert, die in derSTORING
-Klausel vonCREATE SEARCH INDEX
angegeben sind. - Ein Tokenindex, der Tokens docids zuordnet, ähnlich wie umgekehrte Indexe in der Literatur zum Information Retrieval. Spanner verwaltet für jedes
TOKENLIST
des Suchindex einen separaten Tokenindex. Logisch gesehen enthalten Token-Indizes Listen von „docids“ für jedes Token in jeder Partition (in der Informationsbeschaffung als Postingslisten bezeichnet). Die Listen sind nach Tokens sortiert, um einen schnellen Abruf zu ermöglichen. Innerhalb der Listen wird die docid für die Sortierung verwendet. Einzelne Token-Indizes sind ein Implementierungsdetail, das nicht über Spanner-APIs verfügbar gemacht wird.
Spanner unterstützt die folgenden vier Optionen für „docid“.
Google-Suchindex | Docid | Verhalten |
---|---|---|
Die ORDER BY -Klausel wird für den Suchindex ausgelassen. |
{uid} |
Spanner fügt einen verborgenen eindeutigen Wert (UID) hinzu, um jede Zeile zu identifizieren. |
ORDER BY column |
{column, uid} |
Spanner fügt die UID-Spalte als Tiebreaker zwischen Zeilen mit denselben column -Werten innerhalb einer Partition hinzu. |
Verwendungshinweise:
- Die Spalte „Interne UID“ wird nicht über die Spanner API verfügbar gemacht.
- In Indexen, in denen die UID nicht hinzugefügt wird, schlagen Transaktionen fehl, bei denen eine Zeile mit einer bereits vorhandenen (Partition, Sortierreihenfolge) hinzugefügt wird.
Betrachten Sie beispielsweise die folgenden Daten:
AlbumId | SingerId | ReleaseTimestamp | SongTitle |
---|---|---|---|
a1 | 1 | 997 | Schöne Tage |
a2 | 1 | 743 | Schöne Augen |
Angenommen, die Vorsortierungsspalte ist in aufsteigender Reihenfolge sortiert. Dann wird der Inhalt des Tokenindex, der nach SingerId
partitioniert ist, so partitioniert:
SingerId | _token | ReleaseTimestamp | uid |
---|---|---|---|
1 | schön | 743 | uid1 |
1 | schön | 997 | uid2 |
1 | Tage | 743 | uid1 |
1 | Augen | 997 | uid2 |
Sharding des Suchindex
Wenn Spanner eine Tabelle aufteilt, werden die Suchindexdaten so verteilt, dass sich alle Tokens in einer bestimmten Basistabellenzeile im selben Split befinden. Der Suchindex ist also dokumentbasiert partitioniert. Diese Sharding-Strategie hat erhebliche Auswirkungen auf die Leistung:
- Die Anzahl der Server, mit denen jede Transaktion kommuniziert, bleibt konstant, unabhängig von der Anzahl der Tokens oder der Anzahl der indexierten
TOKENLIST
-Spalten. - Suchanfragen mit mehreren bedingten Ausdrücken werden unabhängig für jede Aufteilung ausgeführt. So wird der Leistungsaufwand vermieden, der mit einem verteilten Join verbunden ist.
Suchindexe haben zwei Verteilungsmodi:
- Einheitliche Fragmentierung (Standardeinstellung) Beim einheitlichen Sharding werden indexierte Daten für jede Zeile der Basistabelle zufällig einem Index-Split einer Partition zugewiesen.
- Fragmentierung nach Sortierreihenfolge. Beim Sharding nach Sortierreihenfolge werden Daten für jede Zeile der Basistabelle einem Index-Split einer Partition basierend auf den
ORDER BY
-Spalten (d. h. den Vorsortierspalten) zugewiesen. Bei einer absteigenden Sortierreihenfolge werden beispielsweise alle Zeilen mit den größten Sortierreihenfolgewerten im ersten Index-Split einer Partition und die nächstgrößte Gruppe von Sortierreihenfolgewerten im nächsten Split angezeigt.
Bei diesen Sharding-Modi gibt es einen Kompromiss zwischen Hotspot-Risiken und den Abfragekosten:
- Einheitliche partitionierte Suchindexe werden empfohlen, wenn Lese- oder Schreibmuster für den Suchindex zu Hotspots führen könnten. Durch einheitliches Sharding werden Hotspots vermieden, da die Lese- und Schreiblast gleichmäßig auf die Splits verteilt wird. Dies kann jedoch die Ressourcennutzung während der Ausführung von Abfragen erhöhen. Bei einheitlichen Shard-Suchindexen müssen bei Abfragen alle Splits innerhalb einer Partition gelesen werden, da die Daten zufällig verteilt sind. Beim Zugriff auf gleichmäßig shardierte Indexe liest Spanner alle Splits parallel, um die Gesamtlatenz der Abfrage zu verringern.
- Nach Sortierreihenfolge partitionierte Suchindexe sind vorzuziehen, wenn Lese- oder Schreibmuster wahrscheinlich keine Hotspots verursachen. Mit diesem Ansatz können Sie die Kosten für Abfragen reduzieren, deren
ORDER BY
mit demORDER BY
des Index übereinstimmt und für die ein relativ niedrigerLIMIT
angegeben ist. Beim Ausführen solcher Abfragen liest Spanner inkrementell ab den ersten Splits einer Partition. Die Abfrage kann abgeschlossen werden, ohne alle Splits zu lesen, wennLIMIT
frühzeitig erfüllt werden kann. Der Sharding-Modus eines Suchindex wird mit der Klausel
OPTIONS
konfiguriert.
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);
PostgreSQL
Der Sharding-Modus eines Suchindex wird mit der Klausel WITH
konfiguriert.
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
WITH (sort_order_sharding = true);
Wenn sort_order_sharding=false
festgelegt oder nicht angegeben ist, wird der Suchindex mit einheitlichem Sharding erstellt.
Verschränkte Suchindexe
Wie bei sekundären Indexen können Sie Suchindexe in einer übergeordneten Tabelle der Basistabelle verschachteln. Der Hauptgrund für die Verwendung von verschränkten Suchindexen besteht darin, Daten der Basistabelle mit Indexdaten für kleine Partitionen zusammenzufassen. Diese opportunistische Colocation bietet folgende Vorteile:
- Für Schreibvorgänge ist kein Two-Phase-Commit erforderlich.
- Rückverknüpfungen des Suchindex mit der Basistabelle werden nicht verteilt.
Für verschachtelte Suchindexe gelten die folgenden Einschränkungen:
- Nur nach Sortierreihenfolge partitionierte Indexe können verschachtelt werden.
- Suchindexe können nur in Tabellen der obersten Ebene (nicht in untergeordneten Tabellen) verschachtelt werden.
- Wie bei verschachtelten Tabellen und sekundären Indexen muss der Schlüssel der übergeordneten Tabelle ein Präfix der
PARTITION BY
-Spalten im verschachtelten Suchindex sein.
Verschränkten Suchindex definieren
Im folgenden Beispiel wird gezeigt, wie ein verschachtelter Suchindex definiert wird:
GoogleSQL
CREATE TABLE Singers (
SingerId INT64 NOT NULL
) PRIMARY KEY(SingerId);
CREATE TABLE Albums (
SingerId INT64 NOT NULL,
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN
) PRIMARY KEY(SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
PARTITION BY SingerId,
INTERLEAVE IN Singers
OPTIONS (sort_order_sharding = true);
PostgreSQL
CREATE TABLE singers(
singerid bigint NOT NULL
PRIMARY KEY(singerid));
CREATE TABLE albums(
singerid bigint NOT NULL,
albumid character varying NOT NULL,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist
GENERATED ALWAYS
AS (
spanner.tokenize_fulltext(albumtitle)
) VIRTUAL HIDDEN,
PRIMARY KEY(singerid, albumid)),
INTERLEAVE IN PARENT singers ON DELETE CASCADE;
CREATE
SEARCH INDEX albumsindex
ON
albums(albumtitle_tokens)
PARTITION BY singerid INTERLEAVE IN singers WITH(sort_order_sharding = true);
Sortierreihenfolge für den Suchindex
Die Anforderungen an die Definition der Sortierreihenfolge für den Suchindex unterscheiden sich von denen für sekundäre Indexe.
Betrachten Sie beispielsweise die folgende Tabelle:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
ReleaseTimestamp INT64 NOT NULL,
AlbumName STRING(MAX),
AlbumName_Token TOKENLIST AS (TOKEN(AlbumName)) HIDDEN
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
releasetimestamp bigint NOT NULL,
albumname character varying,
albumname_token spanner.tokenlist
GENERATED ALWAYS AS(spanner.token(albumname)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
Die Anwendung kann einen sekundären Index definieren, um Informationen anhand von AlbumName
zu suchen, die nach ReleaseTimestamp
sortiert sind:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
Der entsprechende Suchindex sieht so aus (hier wird die Tokenisierung mit exakter Übereinstimmung verwendet, da sekundäre Indexe keine Volltextsuche unterstützen):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
Die Sortierreihenfolge des Suchindex muss die folgenden Anforderungen erfüllen:
- Verwenden Sie nur
INT64
-Spalten für die Sortierreihenfolge eines Suchindex. Spalten mit beliebigen Größen verbrauchen zu viele Ressourcen im Suchindex, da Spanner neben jedem Token eine docid speichern muss. Insbesondere kann für die Spalte für die Sortierreihenfolge nicht der TypTIMESTAMP
verwendet werden, daTIMESTAMP
eine Nanosekunden-Genauigkeit verwendet, die nicht in eine 64-Bit-Ganzzahl passt. Spalten für die Sortierreihenfolge dürfen nicht
NULL
sein. Es gibt zwei Möglichkeiten, diese Anforderung zu erfüllen:- Deklarieren Sie die Spalte für die Sortierreihenfolge als
NOT NULL
. - Konfigurieren Sie den Index so, dass NULL-Werte ausgeschlossen werden.
- Deklarieren Sie die Spalte für die Sortierreihenfolge als
Häufig wird ein Zeitstempel verwendet, um die Sortierreihenfolge festzulegen. Häufig werden für solche Zeitstempel Mikrosekunden seit der Unix-Epoche verwendet.
Anwendungen rufen in der Regel zuerst die neuesten Daten ab. Dazu verwenden sie einen Suchindex, der in absteigender Reihenfolge sortiert ist.
NULL-gefilterte Suchindexe
In Suchindexen kann mit der WHERE column_name IS NOT NULL
-Syntax Zeilen aus der Basistabelle ausgeschlossen werden. Die NULL-Filterung kann auf Partitionierungsschlüssel, Spalten für die Sortierreihenfolge und gespeicherte Spalten angewendet werden. Das Filtern nach NULL-Werten in gespeicherten Array-Spalten ist nicht zulässig.
Beispiel
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (Genre)
WHERE Genre IS NOT NULL
PostgreSQL
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens)
INCLUDE (genre)
WHERE genre IS NOT NULL
In der Abfrage muss die NULL-Filterbedingung (Genre IS NOT NULL
in diesem Beispiel) in der WHERE
-Klausel angegeben werden. Andernfalls kann das Abfrageoptimierungstool den Suchindex nicht verwenden. Weitere Informationen finden Sie unter Anforderungen an SQL-Abfragen.
Mit der NULL-Filterung für eine generierte Spalte können Sie Zeilen anhand beliebiger Kriterien ausschließen. Weitere Informationen finden Sie unter Teilindex mithilfe einer generierten Spalte erstellen.
Nächste Schritte
- Informationen zur Tokenisierung und zu Spanner-Tokenizer
- Weitere Informationen zu numerischen Indexen
- Weitere Informationen zu JSON-Indexen
- Weitere Informationen zur Indexpartitionierung