Esta página descreve como adicionar índices de pesquisa. A pesquisa de texto completo é executada em entradas no índice de pesquisa.
Como usar índices de pesquisa
É possível criar um índice de pesquisa em qualquer coluna que você queira disponibilizar para pesquisas de texto completo. Para criar um índice de pesquisa, use a
instrução DDL CREATE SEARCH INDEX
. Para atualizar um índice, use a instrução DDL ALTER SEARCH INDEX
. O Spanner
cria e mantém o índice de pesquisa automaticamente, incluindo a adição e
atualização de dados no índice de pesquisa assim que eles mudam no banco de dados.
Partições do índice de pesquisa
Um índice de pesquisa pode ser particionado ou não particionado, dependendo do tipo de consultas que você quer acelerar.
Um exemplo de quando um índice particionado é a melhor escolha é quando o aplicativo consulta uma caixa de e-mail. Cada consulta é restrita a uma caixa de correio específica.
Um exemplo de quando uma consulta não particionada é a melhor escolha é quando há uma consulta em todas as categorias de produtos em um catálogo de produtos.
Casos de uso do índice de pesquisa
Além da pesquisa de texto completo, os índices de pesquisa do Spanner oferecem suporte a:
- Pesquisas de substring, que é um tipo de consulta que procura uma string mais curta (a substring) em um corpo de texto maior.
- Combinar condições em qualquer subconjunto de dados indexados em uma única verificação de índice.
Embora os índices de pesquisa ofereçam suporte à indexação de dados não textuais, como números e strings de correspondência exata, o caso de uso mais comum para um índice de pesquisa é indexar texto em um documento.
Exemplo de índice de pesquisa
Para mostrar os recursos dos índices de pesquisa, suponha que haja uma tabela que armazene informações sobre álbuns de música:
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX)
) PRIMARY KEY(AlbumId);
O Spanner tem várias funções de tokenização que criam
tokens. Para modificar a tabela anterior e permitir que os usuários realizem uma pesquisa de texto completo para encontrar
títulos de álbuns, use a função TOKENIZE_FULLTEXT
para criar tokens de
títulos de álbuns. Em seguida, crie uma coluna que use o tipo de dados TOKENLIST
para armazenar a saída de tokenização de TOKENIZE_FULLTEXT
. Para este exemplo,
criamos a coluna AlbumTitle_Tokens
.
ALTER TABLE Albums
ADD COLUMN AlbumTitle_Tokens TOKENLIST
AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN;
O exemplo a seguir usa o DDL CREATE SEARCH INDEX
para criar um índice de pesquisa
(AlbumsIndex
) nos tokens AlbumTitle
(AlbumTitle_Tokens
):
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens);
Depois de adicionar o índice de pesquisa, use consultas SQL para encontrar álbuns que correspondam aos critérios de pesquisa. Exemplo:
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
Consistência de dados
Quando um índice é criado, o Spanner usa processos automatizados para preencher os dados e garantir a consistência. Quando as gravações são confirmadas, os índices são atualizados na mesma transação. O Spanner executa automaticamente verificações de consistência de dados.
Definições de esquema do índice de pesquisa
Os índices de pesquisa são definidos em uma ou mais colunas TOKENLIST
de uma tabela. Os índices de pesquisa
têm os seguintes componentes:
- Tabela base: a tabela do Spanner que precisa ser indexada.
- Coluna
TOKENLIST
: uma coleção de colunas que definem os tokens que precisam ser indexados. A ordem dessas colunas não é importante.
Por exemplo, na instrução a seguir, a tabela base é "Álbuns". As colunas TOKENLIST
são criadas em AlbumTitle
(AlbumTitle_Tokens
) e Rating
(Rating_Tokens
).
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);
Use a instrução CREATE SEARCH INDEX
abaixo para criar um índice de pesquisa
usando os tokens para AlbumTitle
e Rating
:
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
Os índices de pesquisa têm as seguintes opções:
- Partições: um grupo opcional de colunas que divide o índice de pesquisa. Consultar um índice particionado geralmente é muito mais eficiente do que consultar um índice não particionado. Para mais informações, consulte Índices de pesquisa de partição.
- Coluna de ordem de classificação: uma coluna
INT64
opcional que estabelece a ordem de recuperação do índice de pesquisa. Para mais informações, consulte Ordem de classificação do índice de pesquisa. - Intercalação: assim como os índices secundários, é possível intercalar índices de pesquisa. Os índices de pesquisa intercalados usam menos recursos para gravar e mesclar com a tabela base. Para mais informações, consulte Índices de pesquisa intercalados.
- Cláusula de opções: uma lista de pares de chave-valor que substitui as configurações padrão do índice de pesquisa.
Para mais informações, consulte a referência de CREATE SEARCH INDEX
.
Layout interno dos índices de pesquisa
Um elemento importante da representação interna dos índices de pesquisa é um
docid, que serve como uma representação eficiente de armazenamento da chave primária
da tabela base, que pode ser arbitrariamente longa. Ele também cria a
ordem do layout de dados interno de acordo com as colunas ORDER BY
fornecidas pelo usuário da
cláusula CREATE SEARCH INDEX
. Ele é representado como um ou dois números inteiros de 64 bits.
Os índices de pesquisa são implementados internamente como um mapeamento de dois níveis:
- Tokens para docids
- Docids para chaves primárias da tabela de base
Esse esquema resulta em economias significativas de armazenamento, já que o Spanner
não precisa armazenar a chave primária da tabela base completa para cada
par <token, document>
.
Há dois tipos de índices físicos que implementam os dois níveis de mapeamento:
- Um índice secundário que mapeia chaves de partição e um docid para a chave primária da tabela de base. No exemplo da seção anterior, isso mapeia
{SingerId, ReleaseTimestamp, uid}
para{AlbumId}
. O índice secundário também armazena todas as colunas especificadas na cláusulaSTORING
deCREATE SEARCH INDEX
. - Um índice de token que mapeia tokens para docids, semelhante aos índices invertidos na
literatura de recuperação de informações. O Spanner mantém um
índice de token separado para cada
TOKENLIST
do índice de pesquisa. Logicamente, os índices de token mantêm listas de docids para cada token em cada partição (conhecidas na recuperação de informações como listas de postagens). As listas são ordenadas por tokens para recuperação rápida e, nas listas, o docid é usado para ordenação. Os índices de token individuais são um detalhe de implementação não exposto pelas APIs do Spanner.
O Spanner oferece suporte às quatro opções a seguir para docid.
Índice da Pesquisa | DocID | Comportamento |
---|---|---|
A cláusula ORDER BY foi omitida para o índice de pesquisa |
{uid} |
O Spanner adiciona um valor exclusivo oculto (UID) para identificar cada linha. |
ORDER BY column |
{column, uid} |
O Spanner adiciona a coluna UID como um desempate entre linhas com os mesmos valores de column em uma partição. |
ORDER BY column ... OPTIONS (disable_automatic_uid_column=true) |
{column} |
A coluna UID não foi adicionada. Os valores de column precisam ser exclusivos em uma partição. |
ORDER BY column1, column2 ... OPTIONS (disable_automatic_uid_column=true) |
{column1, column2} |
A coluna UID não foi adicionada. A combinação de valores column1 e column2 precisa ser exclusiva em uma partição. |
Observações sobre o uso:
- A coluna UID interna não é exposta pela API Spanner.
- Em índices em que o UID não é adicionado, as transações que adicionam uma linha com uma já existente (partição,ordem de classificação) falham.
Por exemplo, considere os seguintes dados:
AlbumId | SingerId | ReleaseTimestamp | SongTitle |
---|---|---|---|
a1 | 1 | 997 | Dias lindos |
a2 | 1 | 743 | Olhos lindos |
Supondo que a coluna de pré-classificação esteja em ordem crescente, o conteúdo do índice de token particionado por SingerId
particiona o conteúdo do índice de token da seguinte maneira:
SingerId | _token | ReleaseTimestamp | uid |
---|---|---|---|
1 | linda | 743 | uid1 |
1 | linda | 997 | uid2 |
1 | dias | 743 | uid1 |
1 | olhos | 997 | uid2 |
Fragmentação do índice de pesquisa
Quando o Spanner divide uma tabela, ele distribui dados de índice de pesquisa para que todos os tokens em uma linha de tabela base específica estejam na mesma divisão. Em outras palavras, o índice de pesquisa é dividido por documentos. Essa estratégia de fragmentação tem implicações significativas na performance:
- O número de servidores com que cada transação se comunica permanece
constante, independentemente do número de tokens ou de colunas
TOKENLIST
indexadas. - As consultas de pesquisa que envolvem várias expressões condicionais são executadas de forma independente em cada divisão, evitando a sobrecarga de desempenho associada a uma mesclagem distribuída.
Os índices de pesquisa têm dois modos de distribuição:
- Fragmentação uniforme (padrão). No sharding uniforme, os dados indexados de cada linha da tabela de base são atribuídos aleatoriamente a uma divisão de índice de uma partição.
- Fragmentação de ordem de classificação. No sharding de ordem de classificação, os dados de cada linha da tabela base
são atribuídos a uma divisão de índice de uma partição com base nas colunas
ORDER BY
. Por exemplo, no caso de uma ordem decrescente, todas as linhas com os valores de ordem de classificação mais altos aparecem na primeira divisão de índice de uma partição e o grupo de valores de ordem de classificação mais alto na próxima divisão.
Esses modos de fragmentação têm uma compensação entre os riscos de hotspotting e o custo da consulta:
- Os índices de pesquisa divididos por ordem de classificação tendem a ter pontos de acesso quando o índice é classificado por um carimbo de data/hora. Para mais informações, consulte Escolher uma chave primária para evitar pontos de acesso. Por outro lado, quando a carga de gravação aumenta em uma série de documentos, o sharding uniforme garante que o aumento seja distribuído uniformemente entre os fragmentos.
- A divisão padrão baseada em carga cria divisões adicionais que oferecem proteção adequada contra hotspots. A desvantagem do sharding uniforme é que ele pode usar mais recursos para alguns tipos de consultas.
O modo de fragmentação de um índice de pesquisa é configurado usando a cláusula OPTIONS
:
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);
Quando sort_order_sharding=false
é definido ou deixado sem especificação, o índice de pesquisa é
criado usando o sharding uniforme.
Índices de pesquisa intercalados
Assim como os índices secundários, é possível intercalar índices de pesquisa em uma tabela mãe da tabela base. O principal motivo para usar índices de pesquisa intercalados é colocar dados de tabelas de base com dados de índice para partições pequenas. Essa colocalização oportunista tem as seguintes vantagens:
- As gravações não precisam fazer uma confirmação em duas fases.
- As uniões reversas do índice de pesquisa com a tabela base não são distribuídas.
Os índices de pesquisa intercalados têm as seguintes restrições:
- Somente índices fragmentados de ordem de classificação podem ser intercalados.
- Os índices de pesquisa só podem ser intercalados em tabelas de nível superior, não em tabelas filhas.
- Como tabelas intercaladas e índices secundários, faça com que a chave da
tabela pai seja um prefixo das colunas
PARTITION BY
no índice de pesquisa intercalado.
Definir um índice de pesquisa intercalado
O exemplo a seguir demonstra como definir um índice de pesquisa intercalado:
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);
Ordem de classificação do índice de pesquisa
Os requisitos para a definição da ordem de classificação do índice de pesquisa são diferentes dos índices secundários.
Por exemplo, considere a tabela a seguir:
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);
O aplicativo pode definir um índice secundário para pesquisar informações usando o
AlbumName
classificado por ReleaseTimestamp
:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
O índice de pesquisa equivalente é semelhante ao seguinte (ele usa a tokenização de correspondência exata, já que os índices secundários não oferecem suporte a pesquisas de texto completo):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
A ordem de classificação do índice de pesquisa precisa estar em conformidade com os seguintes requisitos:
- Use apenas colunas
INT64
para a ordem de classificação de um índice de pesquisa. Colunas com tamanhos arbitrários usam muitos recursos no índice de pesquisa porque o Spanner precisa armazenar um docid ao lado de cada token. Especificamente, a coluna de ordem de classificação não pode usar o tipoTIMESTAMP
porqueTIMESTAMP
usa a precisão de nanossegundos, que não cabe em um número inteiro de 64 bits. As colunas de ordem de classificação não podem ser
NULL
. Há duas maneiras de atender a esse requisito:- Declare a coluna de ordem de classificação como
NOT NULL
. - Configure o índice para excluir valores NULL.
- Declare a coluna de ordem de classificação como
Um carimbo de data/hora é usado com frequência para determinar a ordem de classificação. Uma prática comum é usar microssegundos desde a época do Unix para esses carimbos de data/hora.
Os aplicativos geralmente recuperam os dados mais recentes primeiro usando um índice de pesquisa ordenado em ordem decrescente.
Índices de pesquisa filtrados por NULL
Os índices de pesquisa podem usar a sintaxe WHERE column IS NOT NULL
para
excluir linhas da tabela base. A filtragem NULL pode ser aplicada a chaves de particionamento, colunas de ordem de classificação e colunas armazenadas. A filtragem NULL em colunas de matriz armazenadas não é
permitida.
Exemplo
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (Genre)
WHERE Genre IS NOT NULL
A consulta precisa especificar a condição de filtragem NULL (Genre IS NOT NULL
neste exemplo) na cláusula WHERE
. Caso contrário, o otimizador de consultas não poderá
usar o índice de pesquisa. Para mais informações, consulte os requisitos de consulta SQL.
Use a filtragem NULL em uma coluna gerada para excluir linhas com base em qualquer critério arbitrário. Para mais informações, consulte Criar um índice parcial usando uma coluna gerada.
A seguir
- Saiba mais sobre tokenização e tokenizers do Spanner.
- Saiba mais sobre índices numéricos.
- Saiba mais sobre a partição de índices.