En esta página, se describe cómo agregar y usar índices de búsqueda. La búsqueda en el texto completo se ejecuta en las entradas del índice de la Búsqueda.
Cómo usar los índices de la Búsqueda
Puedes crear un índice de búsqueda en cualquier columna que desees que esté disponible para las búsquedas en el texto completo. Para crear un índice de búsqueda, usa la
declaración DDL
CREATE SEARCH INDEX. Para actualizar un índice, usa la
declaración DDL
ALTER SEARCH INDEX. Spanner
compila y mantiene automáticamente el índice de búsqueda, lo que incluye agregar y
actualizar datos en el índice de búsqueda en cuanto cambia en la base de datos.
Particiones del índice de la Búsqueda
Un índice de búsqueda puede ser particionado o no particionado, según el tipo de consultas que quieras acelerar.
Un ejemplo de cuándo un índice particionado es la mejor opción es cuando la aplicación consulta un buzón de correo electrónico. Cada consulta se restringe a un buzón de correo específico.
Un ejemplo de cuándo una consulta sin partición es la mejor opción es cuando hay una consulta en todas las categorías de productos de un catálogo de productos.
Casos de uso del índice de la Búsqueda
Además de la búsqueda en el texto completo, los índices de búsqueda de Spanner admiten lo siguiente:
- Búsquedas de JSON, que es una forma eficiente de indexar y consultar documentos JSON y JSONB.
- Búsquedas de subcadenas, que es un tipo de consulta que busca una cadena más corta (la subcadena) dentro de un cuerpo de texto más grande.
- Combinar condiciones en cualquier subconjunto de datos indexados, incluidas las coincidencias exactas y numéricas, en un solo análisis de índice.
Para obtener más información sobre los casos de uso, consulta Índices de búsqueda frente a índices secundarios.
Ejemplo de índice de la Búsqueda
Para mostrar las capacidades de los índices de búsqueda, supongamos que hay una tabla que almacena información sobre álbumes de música:
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 tiene varias funciones de asignación de token que crean
tokens. Para modificar la tabla anterior y permitir que los usuarios ejecuten una búsqueda en el texto completo para encontrar
títulos de álbumes, usa la
función TOKENIZE_FULLTEXT para crear tokens a partir de los títulos de los álbumes. Luego, crea una columna que use el
tipo de datos TOKENLIST para contener el resultado de la asignación de token de TOKENIZE_FULLTEXT.
Para este ejemplo, creamos la columna 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;
En el siguiente ejemplo, se usa el DDL CREATE SEARCH INDEX para crear un índice de búsqueda (AlbumsIndex) en los tokens AlbumTitle (AlbumTitle_Tokens):
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens);
PostgreSQL
En este ejemplo, se usa
CREATE SEARCH INDEX.
CREATE SEARCH INDEX albumsindex ON albums(albumtitle_tokens);
Después de agregar el índice de búsqueda, usa consultas en SQL para encontrar álbumes que coincidan con los criterios de búsqueda. Por ejemplo:
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
Crea y consulta un índice de búsqueda para un esquema con nombre
Puedes crear un índice de búsqueda en un esquema con nombre, como se muestra en los siguientes ejemplos.
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
En PostgreSQL, no puedes usar nombres completamente calificados para crear índices en esquemas con nombre.
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);
En los siguientes ejemplos, se muestra cómo consultar el índice de búsqueda en el esquema con nombre:
GoogleSQL
SELECT AlbumId
FROM AlbumSchema.Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
En el siguiente ejemplo, se usa una sugerencia de consulta para indicarle al optimizador de consultas que use el índice llamado AlbumsIndex cuando ejecute la consulta.
SELECT AlbumId
FROM AlbumSchema.Albums@{FORCE_INDEX="AlbumSchema.AlbumsIndex"}
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
En PostgreSQL, no puedes usar nombres completamente calificados para crear índices en esquemas con nombre.
SELECT albumid
FROM AlbumSchema.albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
En el siguiente ejemplo, se usa una sugerencia de consulta para indicarle al optimizador de consultas que use el índice llamado albumsindex cuando ejecute la consulta.
SELECT albumid
FROM AlbumSchema.albums /*@ FORCE_INDEX = albumsindex */
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
Coherencia de los datos
Cuando se crea un índice, Spanner usa procesos automatizados para rellenar los datos y garantizar la coherencia. Cuando se confirman las escrituras, los índices se actualizan en la misma transacción. Spanner realiza automáticamente verificaciones de coherencia de los datos.
Definiciones de esquemas de índices de búsqueda
Los índices de búsqueda se definen en una o más columnas TOKENLIST de una tabla. Los índices de búsqueda tienen los siguientes componentes:
- Tabla base: Es la tabla de Spanner que se debe indexar.
- Columna
TOKENLIST: Es una colección de columnas que definen los tokens que necesitan indexación. El orden de estas columnas no es importante.
Por ejemplo, en la siguiente sentencia, la tabla base es Albums. Las columnas
TOKENLIST se crean en AlbumTitle (AlbumTitle_Tokens) y 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));
Usa la siguiente sentencia CREATE SEARCH INDEX para crear un índice de búsqueda
con los tokens de AlbumTitle y 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
Los índices de búsqueda tienen las siguientes opciones:
- Particiones: Es un grupo opcional de columnas que dividen el índice de la búsqueda. Consultar un índice particionado suele ser mucho más eficiente que consultar un índice no particionado. Para obtener más información, consulta Índices de búsqueda de particiones.
- Columna de orden de clasificación: Es una columna
INT64opcional que establece el orden de recuperación del índice de la Búsqueda. Para obtener más información, consulta Orden de clasificación del índice de búsqueda. - Intercalación: Al igual que los índices secundarios, puedes intercalar índices de búsqueda. Los índices de búsqueda intercalados usan menos recursos para escribir y unirse con la tabla base. Para obtener más información, consulta Índices de búsqueda intercalados.
- Cláusula de opciones: Es una lista de pares clave-valor que anula la configuración predeterminada del índice de búsqueda.
Diseño interno de los índices de búsqueda
Un elemento importante de la representación interna de los índices de búsqueda es un
docid, que sirve como una representación eficiente del almacenamiento de la clave primaria
de la tabla base, que puede ser arbitrariamente larga. También es lo que crea el
orden para el diseño de datos interno según las columnas ORDER BY
proporcionadas por el usuario de la sentencia CREATE SEARCH INDEX. Se representa como uno o dos
números enteros de 64 bits.
Los índices de búsqueda se implementan de forma interna como una asignación de dos niveles:
- Tokens a IDs de documentos
- Docids a claves primarias de la tabla base
Este esquema genera ahorros significativos de almacenamiento, ya que Spanner
no necesita almacenar la clave primaria completa de la tabla base para cada
par <token, document>.
Existen dos tipos de índices físicos que implementan los dos niveles de asignación:
- Un índice secundario que asigna claves de partición y un docid a la clave primaria de la tabla base. En el ejemplo de la sección anterior, esto asigna
{SingerId, ReleaseTimestamp, uid}a{AlbumId}. El índice secundario también almacena todas las columnas especificadas en la cláusulaSTORINGdeCREATE SEARCH INDEX. - Un índice de tokens que asigna tokens a IDs de documentos, similar a los índices invertidos en
la literatura de recuperación de información. Spanner mantiene un
índice de tokens independiente para cada
TOKENLISTdel índice de búsqueda. Lógicamente, los índices de tokens mantienen listas de IDs de documentos para cada token dentro de cada partición (conocidas en la recuperación de información como listas de publicaciones). Las listas se ordenan por tokens para una recuperación rápida y, dentro de las listas, se usa docid para ordenar. Los índices de tokens individuales son un detalle de implementación que no se expone a través de las APIs de Spanner.
Spanner admite las siguientes opciones para docid.
| Índice de la Búsqueda | ID del documento | Comportamiento |
|---|---|---|
Se omite la cláusula ORDER BY para el índice de búsqueda |
{uid} |
Spanner agrega un valor único oculto (UID) para identificar cada fila. |
ORDER BY column |
{column, uid} |
Spanner agrega la columna UID como desempate entre las filas con los mismos valores de column dentro de una partición. |
Notas de uso:
- La columna UID interna no se expone a través de la API de Spanner.
- En los índices en los que no se agrega el UID, las transacciones que agregan una fila con un orden de partición y clasificación ya existente fallan.
Por ejemplo, considera los siguientes datos:
| AlbumId | SingerId | ReleaseTimestamp | SongTitle |
|---|---|---|---|
| a1 | 1 | 997 | Días hermosos |
| a2 | 1 | 743 | Ojos hermosos |
Si suponemos que la columna de ordenación previa está en orden ascendente, el contenido del índice de tokens
particionado por SingerId particiona el contenido del índice de tokens de la
siguiente manera:
| SingerId | _token | ReleaseTimestamp | uid |
|---|---|---|---|
| 1 | hermoso | 743 | uid1 |
| 1 | hermoso | 997 | uid2 |
| 1 | días | 743 | uid1 |
| 1 | ojos | 997 | uid2 |
Fragmentación del índice de la Búsqueda
Cuando Spanner divide una tabla, distribuye los datos del índice de búsqueda para que todos los tokens de una fila de tabla base en particular estén en la misma división. En otras palabras, el índice de la Búsqueda se fragmenta por documentos. Esta estrategia de fragmentación tiene implicaciones de rendimiento significativas:
- La cantidad de servidores con los que se comunica cada transacción permanece
constante, independientemente de la cantidad de tokens o de columnas
TOKENLISTindexadas. - Las consultas de búsqueda que involucran varias expresiones condicionales se ejecutan de forma independiente en cada división, lo que evita la sobrecarga de rendimiento asociada con una unión distribuida.
Los índices de búsqueda tienen dos modos de distribución:
- Fragmentación uniforme (predeterminada). En la fragmentación uniforme, los datos indexados de cada fila de la tabla base se asignan de forma aleatoria a una división de índice de una partición.
- Fragmentación por orden de clasificación. En la fragmentación por orden de clasificación, los datos de cada fila de la tabla base
se asignan a una división de índice de una partición según las columnas
ORDER BY(es decir, las columnas de ordenación previa). Por ejemplo, en el caso de un orden descendente, todas las filas con los valores de orden más altos aparecen en la primera división de índice de una partición, y el siguiente grupo más alto de valores de orden, en la siguiente división.
Estos modos de fragmentación conllevan una compensación entre los riesgos de hotspots y el costo de las consultas:
- Se recomiendan los índices de búsqueda de fragmentación uniforme cuando los patrones de lectura o escritura en el índice de búsqueda podrían generar hotspots. La fragmentación uniforme mitiga los hotspots distribuyendo la carga de lectura y escritura de manera uniforme entre las divisiones, pero esto puede aumentar el uso de recursos durante las ejecuciones de consultas como compensación. En los índices de búsqueda con fragmentación uniforme, las consultas deben leer todas las divisiones dentro de una partición, debido a los datos distribuidos de forma aleatoria. Cuando se accede a índices fragmentados de manera uniforme, Spanner lee todas las divisiones en paralelo para reducir la latencia general de las consultas.
- Los índices de búsqueda fragmentados por orden de clasificación son preferibles cuando es poco probable que los patrones de lectura o escritura
provoquen hotspots. Este enfoque puede reducir el costo de las consultas
cuyo
ORDER BYcoincide con elORDER BYdel índice y especifica unLIMITrelativamente bajo. Cuando se ejecutan estas consultas, Spanner lee a partir de las primeras divisiones de una partición de forma incremental, y la consulta puede completarse sin leer todas las divisiones cuandoLIMITse puede satisfacer antes. El modo de fragmentación de un índice de búsqueda se configura con la cláusula
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
El modo de fragmentación de un índice de búsqueda se configura con la cláusula
WITH.
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
WITH (sort_order_sharding = true);
Cuando se establece sort_order_sharding=false o se deja sin especificar, el índice de búsqueda se
crea con fragmentación uniforme.
Índices de búsqueda intercalados
Al igual que los índices secundarios, puedes intercalar índices de búsqueda en una tabla superior de la tabla base. El motivo principal para usar índices de búsqueda intercalados es colocar los datos de la tabla base con los datos del índice para particiones pequeñas. Esta ubicación conjunta oportunista tiene las siguientes ventajas:
- Las escrituras no necesitan realizar una confirmación en dos fases.
- Las uniones inversas del índice de búsqueda con la tabla base no se distribuyen.
Los índices de búsqueda intercalados tienen las siguientes restricciones:
- Solo se pueden intercalar los índices fragmentados por orden de clasificación.
- Los índices de búsqueda solo se pueden intercalar en tablas de nivel superior (no en tablas secundarias).
- Al igual que las tablas intercaladas y los índices secundarios, haz que la clave de la
tabla superior sea un prefijo de las columnas
PARTITION BYen el índice de búsqueda intercalado.
Define un índice de búsqueda intercalado
En el siguiente ejemplo, se muestra cómo definir un índice de búsqueda intercalado:
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);
Orden de clasificación del índice de la Búsqueda
Los requisitos para la definición del orden de clasificación del índice de búsqueda son diferentes de los índices secundarios.
Por ejemplo, considera la siguiente tabla:
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));
La aplicación podría definir un índice secundario para buscar información con el
AlbumName ordenado por ReleaseTimestamp:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
El índice de la Búsqueda equivalente se ve de la siguiente manera (se usa la asignación de token de coincidencia exacta, ya que los índices secundarios no admiten búsquedas en el texto completo):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
El orden de clasificación del índice de la Búsqueda debe cumplir con los siguientes requisitos:
- Solo usa columnas
INT64para el orden de clasificación de un índice de búsqueda. Las columnas que tienen tamaños arbitrarios usan demasiados recursos en el índice de búsqueda porque Spanner necesita almacenar un docid junto a cada token. Específicamente, la columna de orden de clasificación no puede usar el tipoTIMESTAMPporqueTIMESTAMPusa una precisión de nanosegundos que no cabe en un número entero de 64 bits. Las columnas de orden de clasificación no deben ser
NULL. Existen dos maneras de cumplir con este requisito:- Declara la columna de orden de clasificación como
NOT NULL. - Configura el índice para excluir los valores NULL.
- Declara la columna de orden de clasificación como
A menudo, se usa una marca de tiempo para determinar el orden de clasificación. Una práctica común es usar microsegundos desde el tiempo Unix para esas marcas de tiempo.
Por lo general, las aplicaciones recuperan primero los datos más recientes con un índice de búsqueda ordenado de forma descendente.
Debido a que la columna de orden de clasificación forma parte de la clave docid en el índice secundario
y los índices de tokens subyacentes, cada vez que cambia su valor en la tabla base,
Spanner debe volver a escribir la fila del índice secundario correspondiente y
todas las filas del índice de tokens asociadas, lo que genera una sobrecarga de escritura. Si el valor del orden de clasificación cambia con frecuencia, considera omitir esa columna de ORDER BY o usar un valor menos detallado. Por ejemplo, si una columna UpdateTime se actualiza cada
pocos segundos, puedes usar una columna generada que buckets la marca de tiempo en la
hora, como UpdateTimeHours INT64 AS (UNIX_SECONDS(TIMESTAMP_TRUNC(UpdateTime, HOUR))) STORED
(o updatetimehours bigint GENERATED ALWAYS AS (EXTRACT(EPOCH FROM updatetime - '1970-01-01Z'::timestamptz)::bigint / 3600) STORED
en PostgreSQL).
Índices de búsqueda con filtro NULL
Los índices de búsqueda pueden usar la sintaxis WHERE column_name IS NOT NULL
para excluir filas de la tabla base. El filtrado de valores NULL se puede aplicar a las claves de partición,
las columnas de orden de clasificación y las columnas almacenadas. No se permite el filtrado NULL en columnas
de arrays almacenados.
Ejemplo
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 consulta debe especificar la condición de filtrado NULL (Genre IS NOT NULL para
este ejemplo) en la cláusula WHERE. De lo contrario, el optimizador de consultas no puede
usar el índice de búsqueda. Para obtener más información, consulta
Requisitos de las consultas en SQL.
Usar el filtro WHERE <column> IS NOT NULL en la definición del índice garantiza
que solo se indexen los valores no nulos. Esto permite que el optimizador de consultas
use el índice de manera eficaz para las consultas pertinentes, incluso si la condición IS NOT NULL
no está de forma explícita en la cláusula WHERE de la consulta.
Usa el filtrado NULL en una columna generada para excluir filas según cualquier criterio arbitrario. Para obtener más información, consulta Crea un índice parcial con una columna generada.
¿Qué sigue?
- Obtén más información sobre la asignación de token y los asignadores de token de Spanner.
- Obtén más información sobre los índices numéricos.
- Obtén más información sobre los índices JSON.
- Obtén más información sobre la partición de índices.