Suchindexe

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:

  1. Tokens zu Dokument-IDs
  2. 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:

  1. 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 der STORING-Klausel von CREATE SEARCH INDEX angegeben sind.
  2. 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:

  1. 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.
  2. 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 dem ORDER BY des Index übereinstimmt und für die ein relativ niedriger LIMIT 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, wenn LIMIT 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:

  1. Nur nach Sortierreihenfolge partitionierte Indexe können verschachtelt werden.
  2. Suchindexe können nur in Tabellen der obersten Ebene (nicht in untergeordneten Tabellen) verschachtelt werden.
  3. 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:

  1. 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 Typ TIMESTAMP verwendet werden, da TIMESTAMP eine Nanosekunden-Genauigkeit verwendet, die nicht in eine 64-Bit-Ganzzahl passt.
  2. Spalten für die Sortierreihenfolge dürfen nicht NULL sein. Es gibt zwei Möglichkeiten, diese Anforderung zu erfüllen:

    1. Deklarieren Sie die Spalte für die Sortierreihenfolge als NOT NULL.
    2. Konfigurieren Sie den Index so, dass NULL-Werte ausgeschlossen werden.

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