En esta página se describe cómo añadir y usar índices de búsqueda. La búsqueda en todo el texto se realiza en las entradas del índice de búsqueda.
Cómo usar los índices de búsqueda
Puedes crear un índice de búsqueda en las columnas que quieras que estén disponibles para búsquedas de texto completo. Para crear un índice de búsqueda, usa la instrucción DDL CREATE SEARCH INDEX
. Para actualizar un índice, usa la declaración de DDL ALTER SEARCH INDEX
. Spanner crea y mantiene automáticamente el índice de búsqueda, lo que incluye añadir y actualizar datos en el índice de búsqueda en cuanto cambian en la base de datos.
Particiones del índice de búsqueda
Un índice de búsqueda puede particionarse o no particionarse, según el tipo de consultas que quieras acelerar.
Un ejemplo de cuándo es mejor elegir un índice particionado es cuando la aplicación consulta un buzón de correo. Cada consulta se limita a un buzón específico.
Un ejemplo de cuándo es mejor usar una consulta sin particiones es cuando se hace una consulta en todas las categorías de producto de un catálogo de productos.
Casos prácticos de índices de búsqueda
Además de la búsqueda en todo el texto, los índices de búsqueda de Spanner admiten lo siguiente:
- Búsquedas JSON: una forma eficiente de indexar y consultar documentos JSON y JSONB.
- Búsquedas de subcadenas: se trata de un tipo de consulta que busca una cadena más corta (la subcadena) dentro de un texto más largo.
- Combinar condiciones en cualquier subconjunto de datos indexados, incluidas las coincidencias exactas y los datos numéricos, en un solo análisis de índice.
Para obtener más información sobre los casos prácticos, consulta Búsqueda frente a índices secundarios.
Ejemplo de índice de búsqueda
Para mostrar las funciones 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 tokenización que crean tokens. Para modificar la tabla anterior y permitir que los usuarios realicen una búsqueda de 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. A continuación, cree una columna que use el tipo de datos TOKENLIST
para almacenar el resultado de la tokenización de TOKENIZE_FULLTEXT
.
En 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 añadir el índice de búsqueda, usa consultas de SQL para encontrar los á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')
Coherencia de datos
Cuando se crea un índice, Spanner usa procesos automatizados para rellenar los datos y asegurar la coherencia. Cuando se confirman las escrituras, los índices se actualizan en la misma transacción. Spanner realiza automáticamente comprobaciones de coherencia de los datos.
Definiciones de esquema de índice de búsqueda
Los índices de búsqueda se definen en una o varias columnas TOKENLIST
de una tabla. Los índices de búsqueda tienen los siguientes componentes:
- Tabla base: la tabla de Spanner que necesita indexación.
- Columna
TOKENLIST
: conjunto de columnas que definen los tokens que se deben indexar. El orden de estas columnas no es importante.
Por ejemplo, en la siguiente instrucción, 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 instrucción 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: grupo opcional de columnas que dividen el índice de búsqueda. Consultar un índice con particiones suele ser mucho más eficiente que consultar un índice sin particiones. Para obtener más información, consulta Índices de búsqueda de particiones.
- Columna de ordenación: una columna
INT64
opcional que establece el orden de recuperación del índice de búsqueda. Para obtener más información, consulta Orden de clasificación del índice de búsqueda. - Entrelazado: al igual que los índices secundarios, puedes entrelazar índices de búsqueda. Los índices de búsqueda intercalados usan menos recursos para escribir y combinar con la tabla base. Para obtener más información, consulta Índices de búsqueda intercalados.
- Cláusula Options: 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 representación eficiente en cuanto al almacenamiento de la clave principal de la tabla base, que puede ser arbitrariamente larga. También es lo que crea el orden del diseño de los datos internos según las columnas ORDER BY
proporcionadas por el usuario de la instrucción CREATE SEARCH INDEX
. Se representa como uno o dos números enteros de 64 bits.
Los índices de búsqueda se implementan internamente como una asignación de dos niveles:
- Tokens a IDs de documentos
- Docids a claves principales de la tabla base
Este esquema permite ahorrar espacio de almacenamiento de forma significativa, ya que Spanner no tiene que almacenar la clave principal completa de la tabla base para cada par <token, document>
.
Hay 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 principal de la tabla base. En el ejemplo de la sección anterior, se asigna
{SingerId, ReleaseTimestamp, uid}
a{AlbumId}
. El índice secundario también almacena todas las columnas especificadas en la cláusulaSTORING
deCREATE SEARCH INDEX
. - Un índice de tokens que asigna tokens a docids, similar a los índices invertidos de la documentación sobre recuperación de información. Spanner mantiene un índice de tokens independiente para cada
TOKENLIST
del índice de búsqueda. Desde un punto de vista lógico, los índices de tokens mantienen listas de docids de cada token en cada partición (en la recuperación de información se conocen como listas de publicaciones). Las listas se ordenan por tokens para que la recuperación sea 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 cuatro opciones siguientes para docid.
Índice de búsqueda | Docid | Comportamiento |
---|---|---|
Se omite la cláusula ORDER BY del índice de búsqueda |
{uid} |
Spanner añade un valor único oculto (UID) para identificar cada fila. |
ORDER BY column |
{column, uid} |
Spanner añade la columna UID para desempatar entre las filas con los mismos valores de column en una partición. |
Notas sobre el uso:
- La columna UID interna no se expone a través de la API de Spanner.
- En los índices en los que no se añade el UID, las transacciones que añaden una fila con un par (partición,orden de clasificación) que ya existe fallan.
Por ejemplo, tomemos los siguientes datos:
AlbumId | SingerId | ReleaseTimestamp | SongTitle |
---|---|---|---|
a1 | 1 | 997 | Días bonitos |
a2 | 1 | 743 | Ojos bonitos |
Si la columna de preordenación 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 | bonito | 743 | uid1 |
1 | bonito | 997 | uid2 |
1 | días | 743 | uid1 |
1 | ojos | 997 | uid2 |
Fragmentación del índice de búsqueda
Cuando Spanner divide una tabla, distribuye los datos del índice de búsqueda de forma que todos los tokens de una fila de tabla base concreta se encuentren en la misma división. Es decir, el índice de búsqueda se fragmenta por documentos. Esta estrategia de partición tiene implicaciones significativas en el rendimiento:
- El número de servidores con los que se comunica cada transacción se mantiene constante, independientemente del número de tokens o del número de columnas indexadas
TOKENLIST
. - Las consultas de búsqueda que implican varias expresiones condicionales se ejecutan de forma independiente en cada división, lo que evita la sobrecarga de rendimiento asociada a una unión distribuida.
Los índices de búsqueda tienen dos modos de distribución:
- Fragmentación uniforme (opción predeterminada). En la fragmentación uniforme, los datos indexados de cada fila de la tabla base se asignan aleatoriamente 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 en función de las columnas
ORDER BY
(es decir, las columnas de preordenación). Por ejemplo, en el caso de un orden de clasificación descendente, todas las filas con los valores de orden de clasificación más grandes aparecen en la primera división de índice de una partición, y el siguiente grupo más grande de valores de orden de clasificación aparece en la siguiente división.
Estos modos de partición implican un equilibrio entre los riesgos de puntos de acceso y el coste de las consultas:
- Se recomienda usar índices de búsqueda fragmentados uniformes cuando los patrones de lectura o escritura en el índice de búsqueda puedan provocar puntos de acceso. El particionado uniforme mitiga los puntos de acceso distribuyendo la carga de lectura y escritura de forma equitativa entre las divisiones, pero esto puede aumentar el uso de recursos durante las ejecuciones de consultas. En los índices de búsqueda fragmentados uniformes, las consultas deben leer todas las divisiones de una partición debido a que los datos se distribuyen de forma aleatoria. Cuando se accede a índices fragmentados de forma uniforme, Spanner lee todas las divisiones en paralelo para reducir la latencia general de las consultas.
- Los índices de búsqueda fragmentados con orden de clasificación son preferibles cuando es poco probable que los patrones de lectura o escritura provoquen puntos de acceso. Este método puede reducir el coste de las consultas cuyo
ORDER BY
coincida con elORDER BY
del índice y especifique unLIMIT
relativamente bajo. Al ejecutar estas consultas, Spanner lee de forma incremental a partir de las primeras divisiones de una partición y la consulta se puede completar sin leer todas las divisiones cuando se puede satisfacerLIMIT
antes de tiempo. El modo de partición de un índice de búsqueda se configura mediante 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 partición de un índice de búsqueda se configura mediante 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);
Si se define sort_order_sharding=false
o no se especifica, el índice de búsqueda se crea mediante el fragmentado uniforme.
Índices de búsqueda intercalados
Al igual que los índices secundarios, puedes intercalar índices de búsqueda en una tabla principal de la tabla base. El motivo principal para usar índices de búsqueda intercalados es colocar los datos de la tabla base junto con los datos del índice en particiones pequeñas. Esta colocación oportunista tiene las siguientes ventajas:
- Las escrituras no necesitan hacer una confirmación en dos fases.
- Las combinaciones 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 en las tablas intercaladas y los índices secundarios, haz que la clave de la tabla principal sea un prefijo de las columnas
PARTITION BY
del índice de búsqueda intercalado.
Definir 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 búsqueda
Los requisitos para definir el orden de clasificación del índice de búsqueda son diferentes de los de los índices secundarios.
Por ejemplo, consulta 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 puede definir un índice secundario para buscar información mediante el AlbumName
ordenado por ReleaseTimestamp
:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
El índice de búsqueda equivalente tiene el siguiente aspecto (usa la tokenización de concordancia exacta, ya que los índices secundarios no admiten búsquedas de texto completo):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
El orden de clasificación del índice de búsqueda debe cumplir los siguientes requisitos:
- Utiliza solo columnas
INT64
para 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. En concreto, la columna de orden no puede usar el tipoTIMESTAMP
porque este tipo usa una precisión de nanosegundos que no cabe en un entero de 64 bits.TIMESTAMP
Las columnas de ordenación no pueden ser
NULL
. Hay dos formas de cumplir este requisito:- Declara la columna de orden como
NOT NULL
. - Configura el índice para excluir los valores NULL.
- Declara la columna de orden como
A menudo, se usa una marca de tiempo para determinar el orden de clasificación. Una práctica habitual es usar microsegundos desde la época de Unix para estas marcas de tiempo.
Las aplicaciones suelen obtener primero los datos más recientes mediante un índice de búsqueda ordenado de forma descendente.
Índices de búsqueda filtrados por 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 claves de partición, columnas de orden y columnas almacenadas. No se permite el filtrado NULL en columnas de matriz almacenadas.
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
en este ejemplo) en la cláusula WHERE
. De lo contrario, el optimizador de consultas no podrá usar el índice de búsqueda. Para obtener más información, consulta los requisitos de las consultas SQL.
Use el filtro NULL en una columna generada para excluir filas en función de cualquier criterio arbitrario. Para obtener más información, consulta el artículo Crear un índice parcial con una columna generada.
Siguientes pasos
- Consulta información sobre la tokenización y los tokenizadores de Spanner.
- Consulta información sobre los índices numéricos.
- Consulta información sobre los índices JSON.
- Consulta información sobre la partición de índices.