Auf dieser Seite wird beschrieben, wie Sie Suchindexe hinzufügen und verwenden. Die Volltextsuche wird auf Einträge im Suchindex angewendet.
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 die DDL-Anweisung ALTER SEARCH INDEX
, um einen Index zu aktualisieren. Spanner erstellt und verwaltet den Suchindex automatisch. Dazu werden Daten im Suchindex hinzugefügt und aktualisiert, sobald sie sich 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 für den Einsatz eines partitionierten Index ist, wenn die Anwendung ein E-Mail-Postfach abfragt. Jede Abfrage ist auf eine bestimmte Mailbox beschränkt.
Ein Beispiel für eine unpartitionierte Abfrage ist eine Abfrage, die alle Produktkategorien in einem Produktkatalog umfasst.
Anwendungsfälle für den Suchindex
Neben der Volltextsuche unterstützen Spanner-Suchindexe Folgendes:
- JSON-Suchanfragen: Eine effiziente Möglichkeit, JSON- und JSONB-Dokumente zu indexieren und abzufragen.
- Suchanfragen nach Teilstrings: Bei dieser Art von Suchanfrage wird in einem größeren Text nach einer kürzeren Zeichenfolge (dem Teilstring) gesucht.
- Bedingungen für einen beliebigen Teil der indexierten Daten, einschließlich exakter Übereinstimmungen und numerischer Werte, in einem einzigen Indexscan kombinieren.
Weitere Informationen zu Anwendungsfällen finden Sie unter Suchindexe im Vergleich zu sekundären Indexen.
Beispiel für einen Suchindex
Angenommen, es gibt eine Tabelle mit Informationen zu Musikalben:
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, erstellen Sie mit der Funktion TOKENIZE_FULLTEXT
Tokens aus Albumtiteln. Erstellen Sie dann eine Spalte mit dem Datentyp TOKENLIST
, um die Tokenisierungsausgabe von TOKENIZE_FULLTEXT
zu speichern.
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 du den Suchindex hinzugefügt hast, kannst du mithilfe von 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, führt Spanner automatisierte Prozesse aus, um die Daten zu ergänzen und für Konsistenz zu sorgen. Wenn Schreibvorgänge bestätigt 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 haben die folgenden Komponenten:
- Basistabelle: Die Spanner-Tabelle, die indexiert werden muss.
TOKENLIST
-Spalte: Eine Sammlung von Spalten, die die zu indexierenden Tokens definieren. Die Reihenfolge dieser Spalten ist nicht wichtig.
In der folgenden Anweisung ist „Albums“ beispielsweise die Basistabelle. TOKENLIST
-Spalten werden auf 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));
Mit der folgenden CREATE SEARCH INDEX
-Anweisung können Sie einen Suchindex mit den Tokens für AlbumTitle
und Rating
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 stehen folgende Optionen zur Verfügung:
- Partitionen: Optionale Spaltengruppe, die den Suchindex unterteilt. Das Abfragen eines partitionierten Index ist oft deutlich effizienter als das Abfragen eines nicht partitionierten Index. Weitere Informationen finden Sie unter Partitionssuchindexe.
- Spalte für die Sortierreihenfolge: Eine optionale
INT64
-Spalte, die die Reihenfolge des Abrufs aus dem Suchindex festlegt. Weitere Informationen finden Sie unter Sortierreihenfolge des Suchindexes. - Verschachteln: Wie bei sekundären Indexen können Sie auch Suchindexe verschachteln. Für die Schreibvorgänge und die Zusammenführung mit der Basistabelle werden bei interleaved-Suchindexen weniger Ressourcen benötigt. Weitere Informationen finden Sie unter Interleaved Search-Indexe.
- Options-Klausel: Eine Liste von Schlüssel/Wert-Paaren, die die Standardeinstellungen des Suchindexes überschreiben.
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 und beliebig lang sein kann. Außerdem wird damit die Reihenfolge für das interne Datenlayout gemäß den vom Nutzer bereitgestellten ORDER BY
-Spalten der CREATE SEARCH INDEX
-Anweisung erstellt. 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 Speichereinsparungen, da in Spanner nicht der vollständige Primärschlüssel der Basistabelle für jedes <token, document>
-Paar gespeichert werden muss.
Es gibt zwei Arten von physischen Indexen, die die beiden Zuordnungsebenen implementieren:
- Sekundärer Index, der Partitionsschlüssel und eine Dokument-ID dem Primärschlüssel der Basistabelle zuordnet. Im Beispiel im vorherigen Abschnitt wird
{SingerId, ReleaseTimestamp, uid}
so zu{AlbumId}
zugeordnet. Der sekundäre Index speichert außerdem alle Spalten, die in derSTORING
-Klausel vonCREATE SEARCH INDEX
angegeben sind. - Ein Tokenindex, der Tokens Docids zuordnet, ähnlich wie umgekehrte Indexe in der Literatur zum Informationsretrieval. Spanner verwaltet für jede
TOKENLIST
des Suchindexes einen separaten Tokenindex. Logischerweise enthalten Tokenindexe Listen mit Dokument-IDs für jedes Token in jeder Partition (in der Informationssuche als Postingslisten bezeichnet). Die Listen werden für einen schnellen Abruf nach Tokens sortiert. Innerhalb der Listen wird „docid“ für die Sortierung verwendet. Einzelne Tokenindizes sind Implementierungsdetails, die nicht über Spanner APIs freigegeben werden.
Spanner unterstützt die folgenden vier Optionen für „docid“.
Google-Suchindex | Docid | Verhalten |
---|---|---|
ORDER BY -Klausel für den Suchindex ausgelassen |
{uid} |
Spanner fügt jeder Zeile einen versteckten eindeutigen Wert (UID) hinzu, um sie zu identifizieren. |
ORDER BY column |
{column, uid} |
Spanner fügt die UID-Spalte als Entscheidungskriterium für Zeilen mit denselben column -Werten innerhalb einer Partition hinzu. |
ORDER BY column ... OPTIONS (disable_automatic_uid_column=true) |
{column} |
Die Spalte „UID“ wurde nicht hinzugefügt. Die column -Werte müssen innerhalb einer Partition eindeutig sein. |
ORDER BY column1, column2 ... OPTIONS (disable_automatic_uid_column=true) |
{column1, column2} |
Die Spalte „UID“ wurde nicht hinzugefügt. Die Kombination der Werte column1 und column2 muss innerhalb einer Partition eindeutig sein. |
Verwendungshinweise:
- Die Spalte „interne UID“ wird nicht über die Spanner API bereitgestellt.
- Bei Indexen, in denen die UID nicht hinzugefügt wird, schlagen Transaktionen fehl, die eine Zeile mit einer bereits vorhandenen Partitions- oder Sortierreihenfolge hinzufügen.
Betrachten Sie beispielsweise die folgenden Daten:
AlbumId | SingerId | ReleaseTimestamp | SongTitle |
---|---|---|---|
a1 | 1 | 997 | Schöne Tage |
a2 | 1 | 743 | Schöne Augen |
Angenommen, die Spalte für die Vorsortierung ist in aufsteigender Reihenfolge, wird der Inhalt des Tokenindexes, der nach SingerId
partitioniert ist, auf folgende Weise 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 Suchindexes
Wenn Spanner eine Tabelle teilt, werden Suchindexdaten so verteilt, dass sich alle Tokens in einer bestimmten Basistabellenzeile im selben Split befinden. Mit anderen Worten: Der Suchindex ist nach Dokumenten aufgeteilt. Diese Sharding-Strategie hat erhebliche Auswirkungen auf die Leistung:
- Die Anzahl der Server, mit denen jede Transaktion kommuniziert, bleibt unabhängig von der Anzahl der Tokens oder der Anzahl der indexierten
TOKENLIST
-Spalten konstant. - Suchanfragen mit mehreren bedingten Ausdrücken werden unabhängig voneinander für jede Teilung ausgeführt. So wird der Leistungsoverhead vermieden, der mit einem verteilten Join verbunden ist.
Suchindexe haben zwei Bereitstellungsmodi:
- Einheitliche Sharding (Standardeinstellung). Bei der einheitlichen Sharding-Methode werden indexierte Daten für jede Zeile der Basistabelle zufällig einem Index-Split einer Partition zugewiesen.
- Sortierreihenfolge-Sharding Beim Sortierungsreihenfolge-Sharding werden die Daten jeder Zeile der Basistabelle basierend auf den
ORDER BY
-Spalten (d. h. Spalten mit Vorsortierung) einem Indexsplit einer Partition zugewiesen. Bei einer absteigenden Sortierreihenfolge werden beispielsweise alle Zeilen mit den größten Sortierreihenfolgewerten im ersten Indexsplit einer Partition und die nächstgrößere Gruppe von Sortierreihenfolgewerten im nächsten Split angezeigt.
Bei diesen Sharding-Modi besteht ein Kompromiss zwischen Hotspot-Risiken und Abfragekosten:
- Einheitliche, shardierte Suchindexe werden empfohlen, wenn Lese- oder Schreibmuster für den Suchindex zu Hotspots führen können. Durch einheitliches Sharding werden Hotspots vermieden, indem Lese- und Schreiblast gleichmäßig auf die Teilungen verteilt wird. Dies kann jedoch die Ressourcennutzung bei der Abfrageausführung erhöhen. Bei einheitlichen Sharded Search-Indexen müssen bei Abfragen aufgrund der zufällig verteilten Daten alle Teilungen innerhalb einer Partition gelesen werden. Beim Zugriff auf gleichmäßig shardete Indexe liest Spanner alle Splits parallel, um die Gesamtlatenz der Abfrage zu reduzieren.
Sortierungsreihenfolge-Sharded-Suchindexe sind vorzuziehen, wenn Lese- oder Schreibmuster mit hoher Wahrscheinlichkeit keine Hotspots verursachen. Mit diesem Ansatz können die Kosten für Abfragen gesenkt werden, deren
ORDER BY
mit derORDER BY
des Index übereinstimmt und die eine relativ niedrigeLIMIT
angeben. Bei der Ausführung solcher Abfragen liest Spanner ausgehend von den ersten Teilungen einer Partition inkrementell. Die Abfrage kann abgeschlossen werden, ohne alle Teilungen zu lesen, wennLIMIT
frühzeitig erfüllt werden kann.
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 Suchindexes wird mit der WITH
-Klausel 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, Basistabellendaten mit Indexdaten für kleine Partitionen zusammenzuführen. Diese opportunistische Colocation bietet folgende Vorteile:
- Für Schreibvorgänge ist kein zweiphasiger Commit erforderlich.
- Rückverknüpfungen des Suchindexes mit der Basistabelle werden nicht verteilt.
Für interleaved Suchindexe gelten die folgenden Einschränkungen:
- Nur nach Sortierreihenfolge ge shardete Indexe können überlappt 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 Index für die Zwischenspeicherung von Suchanfragen 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 des Suchindex
Die Anforderungen an die Definition der Sortierreihenfolge des Suchindexes 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 mithilfe der AlbumName
nach ReleaseTimestamp
zu suchen:
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 Suchindexes muss die folgenden Anforderungen erfüllen:
- Verwenden Sie nur
INT64
-Spalten für die Sortierreihenfolge eines Suchindexes. Spalten mit beliebiger Größe belegen zu viele Ressourcen im Suchindex, da Spanner neben jedem Token eine DocID speichern muss. Insbesondere kann der TypTIMESTAMP
nicht für die Spalte „Sortierreihenfolge“ verwendet werden, daTIMESTAMP
eine Nanosekundengenauigkeit 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 zu bestimmen. Häufig werden für solche Zeitstempel Mikrosekunden seit der Unix-Epoche verwendet.
In der Regel rufen Anwendungen die neuesten Daten zuerst mithilfe eines Suchindexes ab, der in absteigender Reihenfolge sortiert ist.
Suchindexe mit NULL-Filter
In Suchindexen kann die Syntax WHERE column_name IS NOT NULL
verwendet werden, um Basistabellenzeilen auszuschließen. NULL-Filter können auf Partitionsschlüssel, Sortierreihenfolgenspalten und gespeicherte Spalten angewendet werden. NULL-Filter für gespeicherte Arrayspalten sind 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 sein. Andernfalls kann der Abfrageoptimierer den Suchindex nicht verwenden. Weitere Informationen finden Sie unter Anforderungen an SQL-Abfragen.
Verwenden Sie den NULL-Filter für eine generierte Spalte, um Zeilen basierend auf beliebigen Kriterien auszuschließen. Weitere Informationen finden Sie unter Teilindex mithilfe einer generierten Spalte erstellen.
Nächste Schritte
- Weitere Informationen zur Tokenisierung und zu Spanner-Tokenisierern
- Weitere Informationen zu numerischen Indexen
- Weitere Informationen zu JSON-Indexen
- Weitere Informationen zur Indexpartitionierung