Questa pagina descrive come aggiungere e utilizzare gli indici di ricerca. La ricerca a testo intero viene eseguita sulle voci nell'indice di ricerca.
Come utilizzare gli indici di ricerca
Puoi creare un indice di ricerca per tutte le colonne che vuoi mettere a disposizione per le ricerche a testo intero. Per creare un indice di 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 di ricerca, inclusa l'aggiunta e
l'aggiornamento dei dati nell'indice di ricerca non appena vengono modificati nel database.
Partizioni dell'indice della rete di ricerca
Un indice di 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 email. Ogni query è limitata a una cassetta postale specifica.
Un esempio di quando una query non partizionata è la scelta migliore è quando viene eseguita 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 di ricerca di Spanner supportano quanto segue:
- Ricerca JSON, che è un modo efficiente per indicizzare e eseguire query sui documenti JSON e JSONB.
- Ricerche di sottostringhe, che è 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, inclusi quelli numerici e con corrispondenza esatta, in un'unica scansione dell'indice.
Per ulteriori informazioni sui casi d'uso, consulta Ricerca e indici secondari.
Esempio di indice della Ricerca
Per mostrare le funzionalità degli indici di ricerca, supponiamo che esista una tabella che immagazzina 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 che gli utenti possano 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 utilizzi il tipo di dato TOKENLIST
per contenere l'output della 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;
Il seguente esempio utilizza il linguaggio DDL
CREATE SEARCH INDEX
per creare un indice di 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 di ricerca, utilizza le query SQL per trovare gli album corrispondenti 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')
Coerenza dei dati
Quando viene creato un indice, Spanner utilizza processi automatici per eseguire il backfill dei dati in modo da garantire la coerenza. Quando le scritture vengono confermate, gli indici vengono aggiornati nella stessa transazione. Spanner esegue automaticamente controlli di coerenza dei dati.
Definizioni dello schema dell'indice della Ricerca
Gli indici di ricerca sono definiti in 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 richiedono l'indicizzazione. L'ordine di queste colonne non è importante.
Ad esempio, nell'istruzione seguente, la tabella di base è Album. 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 dichiarazione CREATE SEARCH INDEX
per creare un indice di 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 di ricerca hanno le seguenti opzioni:
- Partizioni: un gruppo facoltativo di colonne che suddivide l'indice di ricerca. Eseguire query su un indice partizionato è spesso molto più efficiente rispetto all'esecuzione di query su un indice non partizionato. Per ulteriori informazioni, consulta Indici di ricerca delle partizioni.
- Colonna Ordine di ordinamento: una colonna
INT64
facoltativa che stabilisce l'ordine di recupero dall'indice di ricerca. Per ulteriori informazioni, consulta Ordine di ordinamento dell'indice di ricerca. - Interlacciamento: come per gli indici secondari, puoi interlacciare gli indici di ricerca. Gli indici di ricerca interlacciati utilizzano meno risorse per la scrittura e l'unione con la tabella di base. Per ulteriori informazioni, consulta la sezione Indici di ricerca interlacciati.
- Clausola opzioni: un elenco di coppie chiave-valore che sostituisce le impostazioni predefinite dell'indice di ricerca.
Layout interno degli indici di ricerca
Un elemento importante della rappresentazione interna degli indici di ricerca è un docid, che funge da rappresentazione efficiente in termini di spazio di archiviazione della chiave primaria della tabella di base e può essere di lunghezza arbitraria. Inoltre, crea l'ordine per il layout dei dati interni in base alle colonne ORDER BY
fornite dall'utente dell'istruzione CREATE SEARCH INDEX
. È rappresentato da uno o due
numeri interi a 64 bit.
Gli indici di ricerca vengono implementati internamente come mappatura a due livelli:
- Token a docid
- Docid alle chiavi primarie della tabella di base
Questo schema consente un risparmio di spazio di archiviazione significativo, in quanto Spanner non deve memorizzare 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,
{SingerId, ReleaseTimestamp, uid}
viene mappato a{AlbumId}
. L'indice secondario immagazzina inoltre tutte le colonne specificate nella clausolaSTORING
diCREATE SEARCH INDEX
. - Un indice di token che mappa i token ai documenti, simile agli indici inversi nella letteratura di recupero delle informazioni. Spanner gestisce un indice dei token distinto per ogni
TOKENLIST
dell'indice di ricerca. Logicamente, gli indici di token gestiscono elenchi di docid per ogni token all'interno di ogni partizione (noti nel recupero delle informazioni come elenchi di posting). Gli elenchi sono ordinati per token per un recupero rapido e, all'interno degli elenchi, viene utilizzato docid per l'ordinamento. Gli indici dei singoli token sono un dettaglio di implementazione non esposto tramite le API Spanner.
Spanner supporta le seguenti quattro opzioni per docid.
Indice della Ricerca | Docid | Comportamento |
---|---|---|
La clausola ORDER BY viene omessa per l'indice di 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 parità tra le righe con gli stessi valori column all'interno di una partizione. |
ORDER BY column ... OPTIONS (disable_automatic_uid_column=true) |
{column} |
La colonna UID non è stata aggiunta. I valori column devono essere univoci all'interno di una partizione. |
ORDER BY column1, column2 ... OPTIONS (disable_automatic_uid_column=true) |
{column1, column2} |
La colonna UID non è stata aggiunta. La combinazione dei valori column1 , column2 deve essere univoca all'interno di una partizione. |
Note sull'utilizzo:
- La colonna UID interna non è esposta tramite l'API Spanner.
- Negli indici in cui l'UID non viene aggiunto, le transazioni che aggiungono una riga con un valore già esistente (partizione,ordine di ordinamento) non vanno a buon fine.
Ad esempio, considera i seguenti dati:
AlbumId | SingerId | ReleaseTimestamp | SongTitle |
---|---|---|---|
a1 | 1 | 997 | Giornate meravigliose |
a2 | 1 | 743 | Occhi belli |
Supponendo che la colonna di preordinamento sia in ordine crescente, i contenuti dell'indice dei token suddivisi per SingerId
suddividono i contenuti dell'indice dei token nel seguente modo:
SingerId | _token | ReleaseTimestamp | uid |
---|---|---|---|
1 | bella | 743 | uid1 |
1 | bella | 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 di 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 di ricerca è suddiviso in parti in base ai documenti. Questa strategia di suddivisione 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
TOKENLIST
indicizzate. - Le query di ricerca che coinvolgono più espressioni condizionali vengono eseguite in modo indipendente su ogni suddivisione, evitando il sovraccarico delle prestazioni associato a un join distribuito.
Gli indici di ricerca hanno due modalità di distribuzione:
- Sharding uniforme (impostazione predefinita). Nel partitioning 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'ordine di ordinamento. Nel partitioning in base all'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 preordinamento). 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 di valori di ordinamento successivo nella suddivisione successiva.
Queste modalità di suddivisione comportano un compromesso tra i rischi di hotspot e il costo delle query:
- Gli indici di ricerca suddivisi in parti uniformi sono consigliati quando i pattern di lettura o scrittura dell'indice di ricerca potrebbero generare hotspot. Lo sharding uniforme riduce gli hotspot distribuendo il carico di lettura e scrittura uniformemente tra le suddivisioni, ma questo potrebbe aumentare l'utilizzo delle risorse durante le esecuzioni delle query. Negli indici di ricerca con suddivisione uniforme, le query devono leggere tutte le suddivisioni all'interno di una partizione, a causa della distribuzione casuale dei dati. Quando si accede a indici con suddivisione uniforme, Spanner legge tutte le suddivisioni in parallelo per ridurre la latenza complessiva delle query.
Gli indici di ricerca suddivisi per ordine di ordinamento sono preferibili quando è improbabile che i pattern di lettura o scrittura causino hotspot. Questo approccio può ridurre il costo delle query il cui
ORDER BY
corrisponde alORDER BY
dell'indice e specifica unLIMIT
relativamente 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 quandoLIMIT
può essere soddisfatto in anticipo.
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 suddivisione di un indice di 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);
Quando sort_order_sharding=false
è impostato o non specificato, l'indice di ricerca viene creato utilizzando la suddivisione uniforme.
Indici di ricerca interlacciati
Come per gli indici secondari, puoi interfoliare gli indici di ricerca in una tabella principale della tabella di base. Il motivo principale per cui utilizzare gli indici di ricerca interlacciati è collocare i dati della tabella di base con i dati dell'indice per piccole partizioni. Questa co-locazione opportunistica presenta i seguenti vantaggi:
- Per le scritture non è necessario eseguire un commit a due fasi.
- Le unioni posteriori dell'indice di ricerca con la tabella di base non sono distribuite.
Gli indici di ricerca interlacciati presentano le seguenti limitazioni:
- Solo gli indici con suddivisione in base all'ordine di ordinamento possono essere interlacciati.
- Gli indici di ricerca possono essere interlacciati solo nelle tabelle di primo livello (non nelle tabelle figlie).
- Come per le tabelle interlacciate e gli indici secondari, imposta la chiave della tabella principale come prefisso delle colonne
PARTITION BY
nell'indice di ricerca interlacciato.
Definire un indice di ricerca interlacciato
L'esempio seguente mostra come definire un indice di ricerca interlacciato:
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 di 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 di 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 di ricerca deve essere conforme ai seguenti requisiti:
- Utilizza le colonne
INT64
solo per l'ordine di ordinamento di un indice di ricerca. Le colonne con dimensioni arbitrarie utilizzano troppe risorse nell'indice di ricerca perché Spanner deve memorizzare un docid accanto a ogni token. Nello specifico, la colonna ordine di ordinamento non può utilizzare il tipoTIMESTAMP
perchéTIMESTAMP
utilizza la precisione a nanosecondi che non è compatibile con un numero intero a 64 bit. Le colonne dell'ordine di ordinamento non devono essere
NULL
. Esistono due modi per soddisfare questo requisito:- Dichiara la colonna dell'ordinamento come
NOT NULL
. - Configura l'indice in modo da escludere i valori NULL.
- Dichiara la colonna dell'ordinamento come
Per determinare l'ordine di ordinamento viene spesso utilizzato un timestamp. È prassi comune utilizzare i microsecondi dall'epoca Unix per questi timestamp.
In genere le applicazioni recuperano prima i dati più recenti utilizzando un indice di ricerca ordinato in ordine decrescente.
Indici di ricerca filtrati per NULL
Gli indici di 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. Non è consentito applicare filtri NULL alle colonne di array memorizzate.
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, lo strumento di ottimizzazione delle query non è in grado di utilizzare l'indice di ricerca. Per ulteriori informazioni, consulta i requisiti delle query SQL.
Utilizza il filtro NULL su una colonna generata per escludere le righe in base a qualsiasi criterio arbitrario. Per ulteriori informazioni, consulta Creare un indice parziale utilizzando una colonna generata.
Passaggi successivi
- Scopri di più sulla tokenizzazione e sui tokenizzatori Spanner.
- Scopri di più sugli indici numerici.
- Scopri di più sugli indici JSON.
- Scopri di più sul partizionamento degli indici.