Questa pagina descrive come aggiungere e utilizzare gli indici della Ricerca. La ricerca a testo intero viene eseguita sulle voci dell'indice della Ricerca.
Come utilizzare gli indici della Ricerca
Puoi creare un indice della Ricerca su qualsiasi colonna che vuoi rendere disponibile per le ricerche a testo intero. Per creare un indice della Ricerca, utilizza l'istruzione DDL CREATE SEARCH INDEX. Per aggiornare un indice, utilizza l'istruzione DDL ALTER SEARCH INDEX. Spanner crea e gestisce automaticamente l'indice della Ricerca, inclusa l'aggiunta e l'aggiornamento dei dati nell'indice della Ricerca non appena cambiano nel database.
Partizioni dell'indice della Ricerca
Un indice della Ricerca può essere partizionato o non partizionato, a seconda del tipo di query che vuoi accelerare.
Un esempio di quando un indice partizionato è la scelta migliore è quando l'applicazione esegue query su una casella di posta. Ogni query è limitata a una casella di posta specifica.
Un esempio di quando una query non partizionata è la scelta migliore è quando esiste una query su tutte le categorie di prodotti in un catalogo dei prodotti.
Casi d'uso dell'indice della Ricerca
Oltre alla ricerca a testo intero, gli indici della Ricerca di Spanner supportano quanto segue:
- Ricerche JSON, un modo efficiente per indicizzare ed eseguire query su documenti JSON e JSONB.
- Ricerche di sottostringhe, un tipo di query che cerca una stringa più breve (la sottostringa) all'interno di un corpo di testo più grande.
- Combinazione di condizioni su qualsiasi sottoinsieme di dati indicizzati, tra cui corrispondenza esatta e numerica, in un'unica scansione dell'indice.
Per saperne di più sui casi d'uso, consulta Confronto tra indici di ricerca e indici secondari.
Esempio di indice della Ricerca
Per mostrare le funzionalità degli indici della Ricerca, supponiamo che esista una tabella che memorizza informazioni sugli album musicali:
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 dispone di diverse funzioni di tokenizzazione che creano token. Per modificare la tabella precedente in modo da consentire agli utenti di eseguire una ricerca a testo intero per trovare i titoli degli album, utilizza la funzione TOKENIZE_FULLTEXT per creare token dai titoli degli album. Quindi crea una colonna che utilizza il tipo di dati TOKENLIST per contenere l'output di tokenizzazione di TOKENIZE_FULLTEXT.
Per questo esempio, creiamo la colonna 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;
L'esempio seguente utilizza il DDL CREATE SEARCH INDEX per creare un indice della Ricerca (AlbumsIndex) sui token AlbumTitle (AlbumTitle_Tokens):
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens);
PostgreSQL
Questo esempio utilizza CREATE SEARCH INDEX.
CREATE SEARCH INDEX albumsindex ON albums(albumtitle_tokens);
Dopo aver aggiunto l'indice della Ricerca, utilizza le query SQL per trovare gli album che corrispondono ai criteri di ricerca. Ad esempio:
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
Crea ed esegui query su un indice della Ricerca per uno schema denominato
Puoi creare un indice della Ricerca su uno schema denominato, come mostrato negli esempi seguenti.
GoogleSQL
CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) STORED HIDDEN,
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumSchema.AlbumsIndex ON AlbumSchema.Albums(AlbumTitle_Tokens);
PostgreSQL
In PostgreSQL, non puoi utilizzare nomi completi per creare indici su schemi denominati.
CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.albums (
albumid character varying NOT NULL,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) STORED HIDDEN,
PRIMARY KEY(albumid)
);
CREATE SEARCH INDEX albumsindex ON AlbumSchema.albums(albumtitle_tokens);
I seguenti esempi mostrano come eseguire query sull'indice della Ricerca nello schema denominato:
GoogleSQL
SELECT AlbumId
FROM AlbumSchema.Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
L'esempio seguente utilizza un suggerimento per la query per indicare all'ottimizzatore delle query di utilizzare l'indice denominato AlbumsIndex durante l'esecuzione della query.
SELECT AlbumId
FROM AlbumSchema.Albums@{FORCE_INDEX="AlbumSchema.AlbumsIndex"}
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
In PostgreSQL, non puoi utilizzare nomi completi per creare indici su schemi denominati.
SELECT albumid
FROM AlbumSchema.albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
L'esempio seguente utilizza un suggerimento per la query per indicare all'ottimizzatore delle query di utilizzare l'indice denominato albumsindex durante l'esecuzione della query.
SELECT albumid
FROM AlbumSchema.albums /*@ FORCE_INDEX = albumsindex */
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
Coerenza dei dati
Quando viene creato un indice, Spanner utilizza processi automatici per eseguire il backfill dei dati e garantire la coerenza. Quando viene eseguito il commit delle scritture, gli indici vengono aggiornati nella stessa transazione. Spanner esegue automaticamente i controlli di coerenza dei dati.
Definizioni dello schema dell'indice della Ricerca
Gli indici della Ricerca sono definiti su una o più colonne TOKENLIST di una tabella. Gli indici di ricerca hanno i seguenti componenti:
- Tabella di base: la tabella Spanner che deve essere indicizzata.
- Colonna
TOKENLIST: una raccolta di colonne che definiscono i token che devono essere indicizzati. L'ordine di queste colonne non è importante.
Ad esempio, nella seguente istruzione, la tabella di base è Albums. Le colonne TOKENLIST vengono create su AlbumTitle (AlbumTitle_Tokens) e Rating (Rating_Tokens).
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));
Utilizza la seguente istruzione CREATE SEARCH INDEX per creare un indice della Ricerca utilizzando i token per AlbumTitle e Rating:
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
Gli indici della Ricerca hanno le seguenti opzioni:
- Partizioni: un gruppo facoltativo di colonne che dividono l'indice della Ricerca. L'esecuzione di query su un indice partizionato è spesso molto più efficiente rispetto all'esecuzione di query su un indice non partizionato. Per saperne di più, consulta Partiziona gli indici della Ricerca.
- Colonna dell'ordinamento: una colonna
INT64facoltativa che stabilisce l'ordine di recupero dall'indice della Ricerca. Per saperne di più, consulta Ordinamento dell'indice della Ricerca. - Interfoliazione: come gli indici secondari, puoi interfoliare gli indici della Ricerca. Gli indici della Ricerca con interleaving utilizzano meno risorse per la scrittura e l'unione con la tabella di base. Per saperne di più, consulta Indici di ricerca con interleaving.
- Clausola OPTIONS: un elenco di coppie chiave-valore che sostituisce le impostazioni predefinite dell'indice della Ricerca.
Layout interno degli indici della Ricerca
Un elemento importante della rappresentazione interna degli indici della Ricerca è un docid, che funge da rappresentazione efficiente per lo spazio di archiviazione della chiave primaria della tabella di base, che può essere di lunghezza arbitraria. Inoltre, crea l'ordine per il layout dei dati interni in base alle colonne ORDER BY dell'istruzione CREATE SEARCH INDEX fornite dall'utente. È rappresentato da uno o due numeri interi a 64 bit.
Gli indici della Ricerca vengono implementati internamente come mappatura a due livelli:
- Token in ID documento
- Docid alle chiavi primarie della tabella di base
Questo schema consente di risparmiare spazio di archiviazione, poiché Spanner non deve archiviare la chiave primaria completa della tabella di base per ogni coppia <token, document>.
Esistono due tipi di indici fisici che implementano i due livelli di mappatura:
- Un indice secondario che mappa le chiavi di partizione e un docid alla chiave primaria della tabella di base. Nell'esempio della sezione precedente, questo mappa
{SingerId, ReleaseTimestamp, uid}a{AlbumId}. L'indice secondario memorizza anche tutte le colonne specificate nella clausolaSTORINGdiCREATE SEARCH INDEX. - Un indice di token che mappa i token ai docid, simile agli indici invertiti nella letteratura sul recupero di informazioni. Spanner gestisce un indice dei token separato per ogni
TOKENLISTdell'indice della Ricerca. Logicamente, gli indici dei token mantengono elenchi di ID documento per ogni token all'interno di ogni partizione (noti nel recupero di informazioni come elenchi di pubblicazioni). Gli elenchi sono ordinati per token per un recupero rapido e, all'interno degli elenchi, viene utilizzato docid per l'ordinamento. I singoli indici dei token sono un dettaglio di implementazione non esposto tramite le API Spanner.
Spanner supporta le seguenti opzioni per docid.
| Indice della Ricerca | Docid | Comportamento |
|---|---|---|
La clausola ORDER BY viene omessa per l'indice della Ricerca |
{uid} |
Spanner aggiunge un valore univoco nascosto (UID) per identificare ogni riga. |
ORDER BY column |
{column, uid} |
Spanner aggiunge la colonna UID come criterio di spareggio tra le righe con gli stessi valori column all'interno di una partizione. |
Note sull'utilizzo:
- La colonna UID interna non viene esposta tramite l'API Spanner.
- Negli indici in cui l'UID non viene aggiunto, le transazioni che aggiungono una riga con una partizione e un ordinamento già esistenti non vanno a buon fine.
Ad esempio, considera i seguenti dati:
| AlbumId | SingerId | ReleaseTimestamp | SongTitle |
|---|---|---|---|
| a1 | 1 | 997 | Beautiful days |
| a2 | 1 | 743 | Occhi bellissimi |
Supponendo che la colonna di pre-ordinamento sia in ordine crescente, i contenuti dell'indice dei token partizionati in base a SingerId vengono partizionati nel seguente modo:
| SingerId | _token | ReleaseTimestamp | uid |
|---|---|---|---|
| 1 | bello | 743 | uid1 |
| 1 | bello | 997 | uid2 |
| 1 | giorni | 743 | uid1 |
| 1 | occhi | 997 | uid2 |
Sharding dell'indice della Ricerca
Quando Spanner suddivide una tabella, distribuisce i dati dell'indice della Ricerca in modo che tutti i token di una determinata riga della tabella di base si trovino nella stessa suddivisione. In altre parole, l'indice della Ricerca è suddiviso in shard di documenti. Questa strategia di sharding ha implicazioni significative sul rendimento:
- Il numero di server con cui comunica ogni transazione rimane costante, indipendentemente dal numero di token o dal numero di colonne
TOKENLISTindicizzate. - Le query di ricerca che coinvolgono più espressioni condizionali vengono eseguite indipendentemente su ogni suddivisione, evitando l'overhead delle prestazioni associato a un join distribuito.
Gli indici della Ricerca hanno due modalità di distribuzione:
- Sharding uniforme (impostazione predefinita). Nello sharding uniforme, i dati indicizzati per ogni riga della tabella di base vengono assegnati in modo casuale a una suddivisione dell'indice di una partizione.
- Sharding in base all'ordinamento. Nello sharding con ordinamento, i dati di ogni riga della tabella di base vengono assegnati a una suddivisione dell'indice di una partizione in base alle colonne
ORDER BY(ovvero le colonne di pre-ordinamento). Ad esempio, nel caso di un ordinamento decrescente, tutte le righe con i valori di ordinamento più grandi vengono visualizzate nella prima suddivisione dell'indice di una partizione e il gruppo successivo di valori di ordinamento più grandi nella suddivisione successiva.
Queste modalità di sharding comportano un compromesso tra i rischi di hotspot e il costo delle query:
- Gli indici della Ricerca con sharding uniforme sono consigliati quando i pattern di lettura o scrittura nell'indice della Ricerca potrebbero causare hotspot. Lo sharding uniforme riduce gli hotspot distribuendo il carico di lettura e scrittura in modo uniforme tra le suddivisioni, ma ciò potrebbe aumentare l'utilizzo delle risorse durante le esecuzioni delle query come compromesso. Negli indici della Ricerca con sharding uniforme, le query devono leggere tutte le suddivisioni all'interno di una partizione, a causa dei dati distribuiti in modo casuale. Quando accede a indici con sharding uniforme, Spanner legge tutte le suddivisioni in parallelo per ridurre la latenza complessiva delle query.
- Gli indici della Ricerca con sharding in base all'ordinamento sono preferibili quando è improbabile che i pattern di lettura o scrittura causino hotspot. Questo approccio può ridurre il costo delle query la cui
ORDER BYcorrisponde allaORDER BYdell'indice e specifica unLIMITrelativamente basso. Quando esegue queste query, Spanner legge in modo incrementale a partire dalle prime suddivisioni di una partizione e la query può essere completata senza leggere tutte le suddivisioni quandoLIMITpuò essere soddisfatto in anticipo. La modalità di sharding di un indice della Ricerca viene configurata utilizzando la clausola
OPTIONS.
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);
PostgreSQL
La modalità di sharding di un indice della Ricerca viene configurata utilizzando la clausola WITH.
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
WITH (sort_order_sharding = true);
Se sort_order_sharding=false è impostato o non viene specificato, l'indice della Ricerca viene creato utilizzando lo sharding uniforme.
Indici della Ricerca con interleaving
Come gli indici secondari, puoi interfoliare gli indici della Ricerca in una tabella principale della tabella di base. Il motivo principale per utilizzare gli indici della Ricerca con interfoliazione è collocare i dati della tabella di base con i dati dell'indice per le partizioni di piccole dimensioni. Questa collocazione opportunistica presenta i seguenti vantaggi:
- Le scritture non devono eseguire un commit in due fasi.
- I back-join dell'indice della Ricerca con la tabella di base non vengono distribuiti.
Gli indici della Ricerca con interleaving presentano le seguenti limitazioni:
- È possibile eseguire l'interfoliazione solo degli indici con sharding per ordinamento.
- Gli indici della Ricerca possono essere intercalati solo nelle tabelle di primo livello (non nelle tabelle secondarie).
- Come per le tabelle intercalate e gli indici secondari, imposta la chiave della tabella principale come prefisso delle colonne
PARTITION BYnell'indice di ricerca intercalato.
Definisci un indice della Ricerca con interleaving
L'esempio seguente mostra come definire un indice della Ricerca con interleaving:
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);
Ordinamento dell'indice della Ricerca
I requisiti per la definizione dell'ordinamento dell'indice della Ricerca sono diversi da quelli degli indici secondari.
Ad esempio, considera la seguente tabella:
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));
L'applicazione potrebbe definire un indice secondario per cercare informazioni utilizzando AlbumName ordinato per ReleaseTimestamp:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
L'indice della Ricerca equivalente è il seguente (utilizza la tokenizzazione con corrispondenza esatta, poiché gli indici secondari non supportano le ricerche a testo intero):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
L'ordinamento dell'indice della Ricerca deve soddisfare i seguenti requisiti:
- Utilizza solo le colonne
INT64per l'ordinamento di un indice della Ricerca. Le colonne con dimensioni arbitrarie utilizzano troppe risorse nell'indice della Ricerca perché Spanner deve archiviare un docid accanto a ogni token. In particolare, la colonna dell'ordinamento non può utilizzare il tipoTIMESTAMPperchéTIMESTAMPutilizza la precisione in nanosecondi, che non rientra in un numero intero a 64 bit. Le colonne di ordinamento non devono essere
NULL. Esistono due modi per soddisfare questo requisito:- Dichiara la colonna di ordinamento come
NOT NULL. - Configura l'indice in modo da escludere i valori NULL.
- Dichiara la colonna di ordinamento come
Spesso viene utilizzato un timestamp per determinare l'ordinamento. Una pratica comune è utilizzare i microsecondi dall'epoca di Unix per questi timestamp.
In genere, le applicazioni recuperano prima i dati più recenti utilizzando un indice della Ricerca ordinato in ordine decrescente.
Indici dell'indice della Ricerca filtrati per NULL
Gli indici della Ricerca possono utilizzare la sintassi WHERE column_name IS NOT NULL per escludere le righe della tabella di base. Il filtro NULL può essere applicato alle chiavi di partizionamento, alle colonne di ordinamento e alle colonne archiviate. Il filtro NULL sulle colonne di array archiviate non è consentito.
Esempio
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
La query deve specificare la condizione di filtro NULL (Genre IS NOT NULL per questo esempio) nella clausola WHERE. In caso contrario, Query Optimizer non è in grado di utilizzare l'indice della Ricerca. Per saperne di più, consulta i requisiti delle query SQL.
L'utilizzo del filtro WHERE <column> IS NOT NULL nella definizione dell'indice garantisce che vengano indicizzati solo i valori non nulli. In questo modo, l'ottimizzatore delle query può utilizzare l'indice in modo efficace per le query pertinenti, anche se la condizione IS NOT NULL non è esplicitamente presente nella clausola WHERE della query.
Utilizza il filtro NULL su una colonna generata per escludere le righe in base a criteri arbitrari. Per saperne di più, consulta Crea un indice parziale utilizzando una colonna generata.
Passaggi successivi
- Scopri di più sulla tokenizzazione e sui tokenizzatori di Spanner.
- Scopri di più sugli indici numerici.
- Scopri di più sugli indici JSON.
- Scopri di più sul partizionamento degli indici.