Ajusta el rendimiento de las consultas

En esta página, se describen las técnicas para ajustar el rendimiento de las consultas de Spanner. Antes de comenzar a ajustar tus consultas, revisa las siguientes páginas, en las que se explican los conceptos y las herramientas fundamentales que se usan en el ajuste de consultas:

Las consultas y los planes de ejecución en esta página se basan en el siguiente esquema de base de datos:

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate  DATE
) PRIMARY KEY(SingerId);

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  MarketingBudget INT64
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);

CREATE TABLE Songs (
  SingerId  INT64 NOT NULL,
  AlbumId   INT64 NOT NULL,
  TrackId   INT64 NOT NULL,
  SongName  STRING(MAX),
  Duration  INT64,
  SongGenre STRING(25)
) PRIMARY KEY(SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums;

CREATE INDEX SongsBySongName ON Songs(SongName);

CREATE TABLE Concerts (
  VenueId      INT64 NOT NULL,
  SingerId     INT64 NOT NULL,
  ConcertDate  DATE NOT NULL,
  BeginTime    TIMESTAMP,
  EndTime      TIMESTAMP,
  TicketPrices ARRAY<INT64>
) PRIMARY KEY(VenueId, SingerId, ConcertDate);

Puedes usar las siguientes declaraciones del lenguaje de manipulación de datos (DML) para agregar datos a estas tablas:

INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate)
VALUES (1, "Marc", "Richards", "1970-09-03"),
       (2, "Catalina", "Smith", "1990-08-17"),
       (3, "Alice", "Trentor", "1991-10-02"),
       (4, "Lea", "Martin", "1991-11-09"),
       (5, "David", "Lomond", "1977-01-29");

INSERT INTO Albums (SingerId, AlbumId, AlbumTitle)
VALUES (1, 1, "Total Junk"),
       (1, 2, "Go, Go, Go"),
       (2, 1, "Green"),
       (2, 2, "Forever Hold Your Peace"),
       (2, 3, "Terrified"),
       (3, 1, "Nothing To Do With Me"),
       (4, 1, "Play");

INSERT INTO Songs (SingerId, AlbumId, TrackId, SongName, Duration, SongGenre)
VALUES (2, 1, 1, "Let's Get Back Together", 182, "COUNTRY"),
       (2, 1, 2, "Starting Again", 156, "ROCK"),
       (2, 1, 3, "I Knew You Were Magic", 294, "BLUES"),
       (2, 1, 4, "42", 185, "CLASSICAL"),
       (2, 1, 5, "Blue", 238, "BLUES"),
       (2, 1, 6, "Nothing Is The Same", 303, "BLUES"),
       (2, 1, 7, "The Second Time", 255, "ROCK"),
       (2, 3, 1, "Fight Story", 194, "ROCK"),
       (3, 1, 1, "Not About The Guitar", 278, "BLUES");

Recomendaciones de comparativas

Las estadísticas de rendimiento más confiables provienen de la ejecución de una prueba de carga que usa valores de parámetros de consulta diferentes y aleatorios de una instancia de máquina virtual (VM) de Google Cloud que reside en la misma región que la instancia de destino.

Evita depender de Spanner Studio o del modo QueryMode.PROFILE para comparar las consultas, ya que estas herramientas no replican los entornos de red y cliente de producción reales. Sin embargo, después de que una prueba de carga identifica un problema de rendimiento, los planes de consulta (ya sea que se muestreen en Estadísticas de consultas o se ejecuten a pedido en Spanner Studio) son herramientas útiles para investigar ese problema específico.

Comprende los planes de ejecución de consultas

En el visualizador del plan de consulta, se muestra un plan de ejecución de consultas como un árbol de operadores, en el que cada operador lee filas de sus operadores secundarios, las procesa y pasa los resultados a su operador superior.

Para la mayoría de los operadores del plan visual, la latencia informada es aditiva (solo refleja el tiempo que se pasa en ese operador). Sin embargo, para los operadores Distributed union y Distributed apply, la latencia informada es acumulativa: incluye el tiempo invertido en el operador distribuido y el tiempo invertido en sus operadores secundarios. Para obtener más información sobre estos operadores, consulta Unión distribuida y Aplicación distribuida.

Por ejemplo, en el siguiente plan de unión distribuida, la tarjeta Distributed union informa una latencia total de 8.46 ms, mientras que su operador secundario Stream aggregate informa 3.51 ms y Table scan informa 1 ms. Debido a que la latencia de Distributed union es acumulativa, el operador Distributed union en sí agrega solo alrededor de 5 ms de latencia, en lugar de 8 ms:

Un plan de ejecución de unión distribuida con latencia acumulativa.

En el siguiente ejemplo, la tarjeta Distributed union informa 13.69 ms de latencia, mientras que sus operadores secundarios (Stream aggregate en <1 ms, Cross apply en 1.14 ms, Table scan en Albums en <1 ms, Filter scan en 1 ms y Table scan en Singers en 10.85 ms) representan aproximadamente 13 ms. Como resultado, el operador Distributed union agrega menos de 1 ms de latencia, en lugar de 13 ms:

Un segundo plan de ejecución de unión distribuida con latencia
acumulativa.

También puedes ver los valores de latencia acumulados en el panel de resumen del operador.

Combina varios índices

Cuando una consulta filtra varias columnas indexadas con condiciones OR o AND, Spanner a veces puede combinar varios índices secundarios para evitar analizar la tabla base completa. En los ejemplos de esta sección, se extiende la tabla Albums del esquema de ejemplo anterior agregando una columna ReleaseDate y un índice secundario AlbumsByReleaseDate:

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  ReleaseDate     DATE,
  MarketingBudget INT64
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

CREATE INDEX AlbumsByReleaseDate ON Albums(ReleaseDate);

Considera la siguiente consulta, que usa una condición OR para filtrar predicados de dos índices secundarios independientes (AlbumsByAlbumTitle y AlbumsByReleaseDate):

SELECT a.AlbumId, a.AlbumTitle, a.ReleaseDate
FROM Albums AS a
WHERE a.AlbumTitle = 'Play' OR a.ReleaseDate = '2023-09-07';

Cuando una consulta tiene una condición OR en predicados de dos índices separados, Spanner puede usar la optimización basada en costos para leer ambos índices y combinar los resultados (una unión de índices). También puedes forzar esta optimización agregando la sugerencia de tabla INDEX_STRATEGY=FORCE_INDEX_UNION a la consulta:

SELECT a.AlbumId, a.AlbumTitle, a.ReleaseDate
FROM Albums@{INDEX_STRATEGY=FORCE_INDEX_UNION} AS a
WHERE a.AlbumTitle = 'Play' OR a.ReleaseDate = '2023-09-07';

Para obtener más información sobre las sugerencias de tablas, consulta Sugerencias de tablas.

En el siguiente diagrama del plan de ejecución, se muestra cómo Spanner evalúa esta condición OR (disyunción) analizando cada rama por separado y combinando los resultados con los operadores Union all y Hash aggregate:

Un plan de ejecución de disyunción de índices.

A continuación, considera una consulta que use una condición AND para filtrar ambas columnas indexadas:

SELECT a.AlbumId, a.ReleaseDate, a.AlbumTitle
FROM Albums AS a
WHERE a.AlbumTitle = 'Aardvark' AND a.ReleaseDate = '2023-09-07';

Con una condición AND, Spanner elige un solo índice de forma predeterminada y, luego, se une a la tabla base, si es necesario, para evaluar la condición restante en la cláusula AND. Para determinar si esta unión inversa de la tabla base es un cuello de botella de rendimiento, inspecciona el plan de ejecución de la consulta en el visualizador: si el operador Index scan devuelve muchas filas que, luego, se filtran después de unirse con el operador Table scan en la tabla base (lo que genera una latencia o un tiempo de CPU altos en la rama Table scan en relación con la cantidad final de filas devueltas), la unión inversa es un cuello de botella.

Si la unión posterior de la tabla base es el cuello de botella de la consulta, la solución recomendada es agregar la columna adicional en la condición AND a la cláusula STORING del índice secundario para que Spanner pueda evaluar ambas condiciones directamente desde el índice sin leer la tabla base:

CREATE INDEX AlbumsByAlbumTitleStoringReleaseDate
  ON Albums(AlbumTitle)
  STORING (ReleaseDate);

Si no es viable modificar el índice, una alternativa es volver a escribir la consulta para realizar una intersección de ambos índices con INTERSECT DISTINCT y, luego, unir las claves primarias coincidentes a la tabla base Albums:

SELECT union_a.AlbumId, a.ReleaseDate, a.AlbumTitle FROM
  (SELECT a1.SingerId, a1.AlbumId
  FROM Albums @{force_index=AlbumsByReleaseDate} AS a1
  WHERE a1.ReleaseDate = '2023-09-07'
  INTERSECT DISTINCT
  SELECT a2.SingerId, a2.AlbumId
  FROM Albums @{force_index=AlbumsByAlbumTitle} AS a2
  WHERE a2.AlbumTitle = 'Aardvark') AS union_a
JOIN Albums AS a
  ON a.SingerId = union_a.SingerId AND a.AlbumId = union_a.AlbumId;

En el siguiente diagrama del plan de ejecución, se muestra la consulta reescrita. Observa cómo el plan usa dos operadores Index scan (on AlbumsByAlbumTitle y on AlbumsByReleaseDate) para encontrar las claves de intersección antes de realizar la Table scan en la tabla base Albums:

Un plan de ejecución de conjunción de índices.

Si el filtrado en varias columnas es un patrón frecuente en tu carga de trabajo, considera usar la búsqueda en el texto completo de varias columnas en lugar de índices secundarios. Para obtener más información, consulta Realiza una búsqueda de varias columnas.

Optimiza las uniones

Cuando Spanner ejecuta una consulta que une varias tablas, el optimizador de consultas elige el orden de unión (la secuencia en la que se unen las tablas) y el método de unión (el algoritmo que se usa para combinar filas de dos entradas, como una unión de aplicación, una unión de hash o una unión de combinación). Si una consulta con una unión es más lenta de lo esperado, puedes usar una sugerencia de consulta para indicarle a Spanner que use un orden de unión o un método de unión diferente. Esto cambia el plan de ejecución, pero no los resultados que se muestran. Por ejemplo:

GoogleSQL

Orden de unión forzada:

SELECT *
FROM Singers AS s JOIN@{FORCE_JOIN_ORDER=TRUE} Albums AS a
ON s.SingerId = a.SingerId
WHERE s.LastName LIKE '%x%' AND a.AlbumTitle LIKE '%love%';

Cambia el método de unión:

SELECT *
FROM Singers AS s JOIN@{JOIN_METHOD=HASH_JOIN} Albums AS a
ON s.SingerId = a.SingerId;

PostgreSQL

Orden de unión forzada:

SELECT *
FROM Singers AS s JOIN/*@ FORCE_JOIN_ORDER=TRUE */ Albums AS a
ON s.SingerId = a.SingerId
WHERE s.LastName LIKE '%x%' AND a.AlbumTitle LIKE '%love%';

Cambia el método de unión:

SELECT *
FROM Singers AS s JOIN/*@ JOIN_METHOD=HASH_JOIN */ Albums AS a
ON s.SingerId = a.SingerId;

En las siguientes secciones, se proporcionan antecedentes que pueden ayudarte a determinar qué órdenes y métodos de unión pueden mejorar el rendimiento de las consultas.

Método de unión y análisis

Spanner admite varios métodos de unión, y la elección del método afecta directamente la forma en que Spanner analiza las tablas o los índices subyacentes:

  • Aplicar unión (conocida en algunos sistemas como unión de búsqueda o unión de bucle anidado): ejecuta su lado derecho ("mapa") una vez para cada fila del lado izquierdo ("entrada"). Debido a que cada búsqueda de la derecha usa valores de la fila de la izquierda actual, las columnas de unión de la izquierda se pueden usar como una clave de búsqueda en la derecha. Una unión de aplicación distribuida funciona de manera similar a una unión de aplicación local, pero agrupa las filas en lotes para una comunicación más eficiente entre las divisiones en una unión distribuida.
  • Unión de hash: Lee filas de una entrada para compilar una tabla de hash en la memoria en las claves de unión y, luego, analiza la otra entrada para buscar coincidencias en la tabla de hash.
  • Unión de combinación: lee ambas entradas en orden de clasificación en las claves de unión y combina las filas que coinciden en un solo pase.

Por ejemplo, considera la siguiente consulta que une Singers y Albums:

SELECT s.*, a.AlbumTitle
FROM Singers AS s
JOIN Albums AS a ON s.SingerId = a.SingerId
WHERE s.SingerId = 2 AND a.AlbumId = 1;

Con una unión de aplicación, s.SingerId = a.SingerId actúa como una condición de búsqueda en el lado derecho de la unión, por lo que la consulta solo tiene que leer una fila de la tabla Albums.

Por el contrario, en una unión hash o de combinación, las entradas izquierda y derecha se leen de forma independiente, por lo que la condición de unión no proporciona una clave de búsqueda de un lado al otro. En el peor de los casos, una unión de hash o combinación realiza un análisis de tabla completa de cada entrada.

En las consultas interactivas y transaccionales, la mayoría de las uniones usan índices, por lo que la aplicación distribuida es el tipo de unión más común en Spanner. Sin embargo, a veces, una unión hash o de combinación es una mejor opción, ya que leer un rango contiguo de filas es mucho más rápido que realizar la misma cantidad de búsquedas dispersas de una sola fila. La compensación es si es más rápido buscar menos filas (con una unión de aplicación) o analizar un rango contiguo más grande de filas (con una unión de hash o combinación). Para obtener más información sobre esta compensación, consulta Lecturas secuenciales frente a búsquedas dispersas.

Para las consultas que leen miles de filas, si hay un índice disponible en la clave de unión, suele ser más seguro usar una unión de aplicación distribuida. Esto se debe a que el costo de la unión de aplicación es proporcional a la cantidad de filas unidas, mientras que con una unión de hash o combinación, el costo puede ser proporcional al tamaño total de la tabla. Para consultas más grandes (como las que procesan millones de filas), usar una unión hash o de combinación podría ofrecer una mejora significativa del rendimiento.

Para obtener más información sobre los operadores y las sugerencias de unión, consulta los siguientes recursos:

Orden de las uniones

Al igual que en otros sistemas de administración de base de datos relacional (RDBMS), ordena las uniones para mantener el tamaño del resultado intermedio lo más pequeño posible. Esto significa filtrar los datos lo antes posible y aplazar las coincidencias grandes lo más tarde posible.

Si proporcionas la sugerencia FORCE_JOIN_ORDER=TRUE, Spanner ejecuta las uniones en el orden exacto en el que aparecen en el texto de la consulta. Mueve las uniones con filtros muy selectivos antes en la consulta y mueve las uniones con coincidencias grandes más adelante en la consulta.

Optimiza las agregaciones

Las siguientes técnicas pueden ayudarte a reducir la cantidad de filas analizadas y ordenadas durante las consultas de agregación.

Recupera una fila por prefijo de clave

Un patrón de agregación común es recuperar una fila por prefijo de clave (por ejemplo, la fila con el número de versión o la marca de tiempo más altos). En GoogleSQL, la forma más eficiente de hacerlo es usar ANY_VALUE con HAVING MAX, que encuentra la fila con el valor máximo y devuelve la columna solicitada en un solo pase de agregación sin requerir una unión de tabla con sí misma ni una ordenación completa. Por ejemplo, en la siguiente consulta, se agrupa la tabla Concerts por ConcertDate y se devuelve el BeginTime del concierto con la EndTime más reciente para cada fecha:

SELECT ConcertDate, ANY_VALUE(BeginTime HAVING MAX(EndTime))
FROM Concerts
GROUP BY ConcertDate;

Verifica la existencia de filas

Para verificar si una tabla tiene filas o filas que coinciden con una condición específica, usa LIMIT en lugar de COUNT. Por ejemplo:

SELECT 1 FROM Albums LIMIT 1;

Aumenta el paralelismo

Spanner divide las tablas y los índices en rangos de claves primarias contiguos llamados divisiones, que se pueden procesar en paralelo en varios servidores. Por lo general, Spanner elige el paralelismo de consultas para equilibrar la latencia y la eficiencia. Por ejemplo, si una consulta tiene una cláusula LIMIT, puede usar menos paralelismo para evitar desperdiciar el trabajo que descarta el límite. La cantidad de divisiones analizadas por la consulta limita el grado máximo de paralelismo.

En el caso de las consultas con límites o análisis grandes, puedes establecer la sugerencia de sentencia USE_ADDITIONAL_PARALLELISM=TRUE para indicarle a Spanner que optimice principalmente la latencia:

GoogleSQL

@{USE_ADDITIONAL_PARALLELISM=TRUE}
SELECT *
FROM Singers
LIMIT 10;

PostgreSQL

/*@ USE_ADDITIONAL_PARALLELISM=TRUE */
SELECT *
FROM Singers
LIMIT 10;

Esta sugerencia podría saturar la CPU en una división determinada. Si la consulta es de larga duración, envíala con prioridad de solicitud media para evitar aumentar la latencia de otras consultas de prioridad alta.

Límites de Optimize

Si una consulta contiene una cláusula LIMIT, pero analiza muchas más filas que el valor límite, verifica las siguientes causas posibles:

Falta el índice

Si una consulta tiene un filtro que no se puede satisfacer con un índice, el plan de consulta analiza la tabla base o un índice que no proporciona el orden de clasificación requerido. Luego, la consulta ordena por completo los datos coincidentes antes de evaluar la cláusula LIMIT.

Para resolver este problema, considera agregar un índice secundario que abarque las columnas de filtro. Si el índice se define como NULL_FILTERED (lo que significa que excluye las filas en las que una columna indexada es NULL), asegúrate de que el predicado de la consulta incluya IS NOT NULL para que el optimizador pueda usar el índice. Para obtener más información sobre cómo crear índices secundarios, consulta Índices secundarios.

Orden completo

Si existe un índice en la columna filtrada, pero el índice no proporciona el orden de clasificación que requiere la cláusula ORDER BY, Spanner debe leer todas las filas que coincidan y pasarlas a un operador Sort en el plan de ejecución de la consulta antes de aplicar el LIMIT.

Para evitar una ordenación completa, especifica las columnas en tu sentencia CREATE INDEX en el siguiente orden:

  • Primero, cualquier columna que tenga una condición de igualdad (=) o IN en la consulta
  • Luego, las mismas columnas que la cláusula ORDER BY de la consulta, en el mismo orden y con la misma dirección de ordenamiento (ASC o DESC)

Por ejemplo, supongamos que tienes una tabla Users (que no se muestra en el esquema de ejemplo anterior) y deseas optimizar la siguiente consulta:

SELECT * FROM Users
WHERE Name='Alex'
ORDER BY LastLogin DESC
LIMIT 10;

Crea un índice en (Name, LastLogin DESC) para que Spanner pueda leer las 10 filas superiores directamente en el orden del índice sin ordenar:

CREATE INDEX UsersByNameLastLogin ON Users(Name, LastLogin DESC);

Ordena por sufijo de clave

Incluso cuando un índice incluye la columna ORDER BY como un sufijo de la clave de índice, una consulta podría analizar filas adicionales si hay un predicado IN, OR o de rango en una de las columnas de prefijo del índice.

Por ejemplo, considera la siguiente consulta que usa el índice (Name, LastLogin DESC) del ejemplo de ordenamiento completo anterior, que no forma parte del esquema de ejemplo:

SELECT *
FROM Users
WHERE Name IN ('Alex','Amal')
ORDER BY LastLogin DESC
LIMIT 10;

Debido a que Name coincide con varios valores, las filas que coinciden no se almacenan en un solo rango ordenado de forma global por LastLogin DESC. Puedes reestructurar la consulta con UNNEST y una unión para que Spanner recupere las filas superiores de cada valor de Name por separado y, luego, las combine:

SELECT *
FROM UNNEST(['Alex','Amal']) AS Name
JOIN Users AS u ON u.Name = Name
ORDER BY LastLogin DESC
LIMIT 10;

Esta reescritura genera análisis más eficientes, ya que lee la cantidad mínima de filas de cada clave de prefijo vinculada ('Alex' y 'Amal'), seguida de una pequeña ordenación de los resultados combinados para producir el LIMIT final.

Un caso especial común de este patrón es un prefijo de fragmento, en el que se agrega una columna ShardId como la columna de clave principal para distribuir las operaciones de escritura entre las divisiones. Por ejemplo, la siguiente consulta analiza 32 fragmentos (de 0 a 31):

SELECT *
FROM Users
WHERE ShardId BETWEEN 0 AND 31
ORDER BY LastLogin DESC
LIMIT 10;

Para optimizar esta consulta, enumera de forma explícita los IDs de fragmento (del 0 al 31) y únelos con la tabla Users usando GENERATE_ARRAY en GoogleSQL o generate_series en PostgreSQL:

GoogleSQL

SELECT *
FROM GENERATE_ARRAY(0, 31) AS id
JOIN Users AS u ON u.ShardId = id
ORDER BY LastLogin DESC
LIMIT 10;

PostgreSQL

SELECT *
FROM generate_series(0, 31) AS id
JOIN Users AS u ON u.ShardId = id
ORDER BY LastLogin DESC
LIMIT 10;

Para obtener más información sobre estas funciones de generación de arrays y series, consulta GENERATE_ARRAY para GoogleSQL y generate_series para PostgreSQL.

Comprende el rendimiento de los análisis

En las siguientes secciones, se describen las causas más comunes de los análisis lentos de tablas o índices, y cómo mitigarlos.

Se eliminaron filas

De forma predeterminada, Spanner conserva las filas borradas o reemplazadas durante una hora para admitir lecturas históricas y la recuperación de un momento determinado, y puedes aumentar este período de retención hasta siete días. Después de que una versión de fila antigua pasa el período de retención, se vuelve apta para quitarse durante la compactación del almacenamiento en segundo plano, lo que puede tardar hasta siete días más. Hasta que se compacte una versión de fila anterior, es posible que las consultas que analizan el rango de claves deban leer las versiones de fila borradas o reemplazadas y filtrarlas. Para obtener más información sobre la retención de datos, consulta Recuperación de un momento determinado (PITR).

Para verificar si una consulta analizó filas borradas, inspecciona la métrica Filas borradas que se analizaron en la vista de detalles del operador Análisis en el panel de detalles del visualizador del plan de consulta.

Para mitigar los análisis lentos causados por una gran cantidad de filas borradas recientemente, puedes activar una compactación de datos manual. Para obtener más información, consulta Compactación manual de datos.

Filas grandes

En el caso de las filas pequeñas (hasta unos cientos de bytes), el costo de un análisis depende principalmente de la cantidad de filas analizadas. A medida que aumenta el tamaño de las filas, el tamaño en bytes de las filas se vuelve más significativo. En el almacenamiento orientado a filas, el tamaño total de la fila afecta el rendimiento del análisis, no solo el tamaño de las columnas proyectadas por la consulta.

Para las consultas analíticas con muchos análisis en tablas grandes, considera usar el motor de columnas. Para obtener más información, consulta Supervisa el motor de columnas.

Para las consultas en conjuntos de filas más estrechos, si una tabla tiene columnas grandes y columnas más pequeñas que se consultan con frecuencia, considera las siguientes opciones:

  • Separa las columnas más grandes en un grupo de localidad no predeterminado (que almacena las columnas especificadas por separado en el disco del resto de la tabla) o en una tabla secundaria separada para que los análisis de las columnas más pequeñas no lean los datos de las columnas grandes. Para obtener más información, consulta Crea y administra grupos de localidad.
  • Para optimizar aún más el tiempo de lectura, duplica las columnas más pequeñas en un índice secundario con la misma clave primaria que la tabla base y usa la cláusula STORING para incluir las columnas más pequeñas.

Por ejemplo, supongamos que una tabla Documents (que no forma parte del esquema de ejemplo anterior) almacena una cadena Metadata pequeña junto con un objeto JSON Content grande. Puedes crear un índice secundario que almacene solo Metadata para que las consultas que no necesitan Content puedan analizar el índice más pequeño en lugar de la tabla base:

CREATE TABLE Documents (
  Id INT64,
  Metadata STRING(MAX),
  Content JSON
) PRIMARY KEY (Id);

CREATE INDEX DocumentMetadata ON Documents(Id) STORING (Metadata);

Datos intercalados grandes

Cuando las tablas se intercalan, Spanner almacena físicamente cada fila superior junto con sus filas secundarias intercaladas en orden de clave. Si una tabla superior tiene muchas filas secundarias intercaladas o una tabla secundaria tiene muchas menos filas que sus tablas superiores o hermanas, los análisis de rango en la tabla más pequeña son más dispersos y menos eficientes, ya que el análisis debe omitir las filas intercaladas de las otras tablas.

Por ejemplo, considera el siguiente esquema (que no forma parte del esquema de ejemplo anterior), en el que Transaction y AuthorizedPerson se intercalan en Account:

CREATE TABLE Account (
  AccountId INT64,
  -- Additional columns omitted
) PRIMARY KEY (AccountId);

CREATE TABLE Transaction (
  AccountId INT64,
  TransactionId STRING(MAX),
  -- Additional columns omitted
) PRIMARY KEY (AccountId, TransactionId),
INTERLEAVE IN PARENT Account;

CREATE TABLE AuthorizedPerson (
  AccountId INT64,
  SignerId INT64,
  Name STRING(MAX),
  -- Additional columns omitted
) PRIMARY KEY (AccountId, SignerId),
INTERLEAVE IN PARENT Account;

Si cada fila de Account tiene miles de filas de Transaction y solo unas pocas filas de AuthorizedPerson, un análisis de rango en Account o AuthorizedPerson tiene un costo por fila más alto que un análisis de rango en Transaction (aunque su costo total sigue siendo más bajo). Esto sucede porque un análisis de Transaction lee muchas filas de Transaction contiguas a la vez, mientras que un análisis de Account o AuthorizedPerson tiene menos filas adyacentes de la misma tabla y debe omitir las filas de Transaction intercaladas.

Para optimizar los análisis de rango en la tabla más dispersa (Account o AuthorizedPerson), crea un índice secundario no intercalado que duplique las columnas que necesita tu consulta, de forma similar al enfoque para las filas grandes:

CREATE INDEX AuthorizedPersonByAccount
  ON AuthorizedPerson(AccountId, SignerId)
  STORING (Name);

Usa la ejecución orientada a lotes

Spanner puede ejecutar análisis de consultas y operadores de consultas con un método orientado a filas (procesando una fila a la vez) o un método orientado a lotes (procesando bloques de filas a la vez). En las siguientes secciones, se explica cómo inspeccionar y configurar los análisis orientados a lotes y la ejecución de operadores orientados a lotes.

Análisis orientados a lotes

Algunas consultas de Spanner podrían beneficiarse del uso de un método de procesamiento orientado a lotes cuando se analizan datos, en lugar del método de procesamiento orientado a filas más común. El procesamiento de análisis por lotes es una forma más eficiente de procesar grandes volúmenes de datos de una sola vez y permite que las consultas logren un menor uso de CPU y una menor latencia.

La operación de análisis de Spanner siempre comienza la ejecución en el método orientado a filas. Durante este tiempo, Spanner recopila varias métricas de tiempo de ejecución. Luego, Spanner aplica un conjunto de heurísticas basadas en el resultado de estas métricas para determinar el método de análisis óptimo. Cuando es apropiado, Spanner cambia a un método de procesamiento orientado a lotes para ayudar a mejorar la capacidad de procesamiento y el rendimiento del análisis.

Casos de uso habituales

Las consultas con las siguientes características suelen beneficiarse del procesamiento orientado a lotes:

  • Análisis grandes de datos que se actualizan con poca frecuencia
  • Análisis con predicados en columnas de ancho fijo
  • Análisis con recuentos de búsqueda grandes (una búsqueda usa un índice para recuperar registros)

Casos de uso sin mejoras de rendimiento

No todas las consultas se benefician del procesamiento orientado a lotes. Los siguientes tipos de consultas tienen un mejor rendimiento con el procesamiento de análisis orientado a filas:

  • Consultas de búsqueda de puntos: Son consultas que recuperan solo una fila.
  • Consultas de análisis pequeñas: Son análisis de tablas que analizan solo unas pocas filas, a menos que tengan recuentos de búsqueda grandes.
  • Consultas con una cláusula LIMIT: Son consultas que dejan de analizar después de devolver una cantidad fija de filas.
  • Consultas que leen datos con alta deserción: Consultas en las que se actualiza con frecuencia más del 10% de los datos leídos.
  • Consultas con filas que contienen valores grandes: Son consultas en filas que contienen valores superiores a 32,000 bytes (antes de la compresión) en una sola columna.

Verifica el método de análisis que usa una consulta

Para verificar si tu consulta usa el procesamiento orientado a lotes, el procesamiento orientado a filas o cambia de forma automática entre los dos métodos de análisis, sigue estos pasos:

  1. En la consola de Google Cloud , ve a la página Instancias de Spanner.

    Ir a la página Instancias

  2. Haz clic en el nombre de la instancia con la consulta que deseas investigar.

  3. En la tabla Bases de datos, haz clic en la base de datos con la consulta que deseas investigar.

  4. En el menú de navegación de Spanner, haz clic en Spanner Studio.

  5. Haz clic en Nueva pestaña del editor de SQL o Nueva pestaña.

  6. En el editor de consultas, ingresa tu consulta.

  7. Haz clic en Ejecutar.

    Spanner ejecuta la consulta y muestra los resultados.

  8. En el panel del editor de consultas, haz clic en la pestaña Explicación.

    Spanner muestra el visualizador del plan de ejecución de consultas. Cada tarjeta del gráfico representa un iterador. Para obtener más información sobre los planes de ejecución, consulta Planes de ejecución de consultas.

  9. Haz clic en la tarjeta del iterador Análisis de tabla para abrir un panel de información.

    En el panel de información, se muestra información contextual sobre el análisis seleccionado, incluido el método de análisis (Método). Automático indica que Spanner determina el método de análisis. Otros valores posibles incluyen Lote para el procesamiento orientado a lotes y Fila para el procesamiento orientado a filas, como se muestra en la siguiente imagen:

    En una tarjeta de análisis de tablas, se muestra el método de análisis como Automático.

Aplica el método de análisis que usa una consulta

Para optimizar el rendimiento de las consultas, Spanner elige el método de análisis óptimo para tu consulta. Google recomienda que uses este método de análisis predeterminado. Sin embargo, puede haber situaciones en las que quieras aplicar un tipo específico de método de análisis.

Aplicar el análisis orientado a lotes

Puedes aplicar el análisis orientado a lotes a nivel de tabla y de sentencia.

Para aplicar el método de análisis orientado a lotes a nivel de la tabla, usa una sugerencia de tabla en tu consulta:

GoogleSQL

SELECT *
FROM (Singers@{SCAN_METHOD=BATCH} AS s
  JOIN Albums AS a ON s.SingerId = a.SingerId);

PostgreSQL

SELECT *
FROM (Singers/*@ scan_method=batch */ AS s
  JOIN Albums AS a ON s.SingerId = a.SingerId);

Para aplicar el método de análisis orientado a lotes a nivel de la instrucción, usa una sugerencia de instrucción en tu consulta:

GoogleSQL

@{SCAN_METHOD=BATCH}
SELECT *
FROM Singers;

PostgreSQL

/*@ scan_method=batch */
SELECT *
FROM Singers;

Cuando usas una sugerencia a nivel de la sentencia, se aplica a toda la consulta. Es preferible establecer la sugerencia a nivel de la tabla, ya que proporciona un control más detallado.

Forzar la búsqueda orientada a filas

Aunque Google no recomienda inhabilitar el método de análisis automático establecido por Spanner, puedes inhabilitarlo y usar el método de análisis orientado a filas para solucionar problemas, como diagnosticar la latencia.

Para inhabilitar la selección automática del método de análisis y aplicar el procesamiento de filas a nivel de la tabla, usa una sugerencia de tabla en tu consulta:

GoogleSQL

SELECT *
FROM (Singers@{SCAN_METHOD=ROW} AS s
  JOIN Albums AS a ON s.SingerId = a.SingerId);

PostgreSQL

SELECT *
FROM (Singers/*@ scan_method=row */ AS s
  JOIN Albums AS a ON s.SingerId = a.SingerId);

Para inhabilitar el método de análisis automático y aplicar el procesamiento de filas a nivel de la sentencia, usa una sugerencia de sentencia en tu consulta:

GoogleSQL

@{SCAN_METHOD=ROW}
SELECT *
FROM Singers;

PostgreSQL

/*@ scan_method=row */
SELECT *
FROM Singers;

Método de ejecución

Además de los análisis orientados a lotes, también puedes optimizar la ejecución de consultas aplicando el método de ejecución a nivel de la sentencia. Esto solo funciona para algunos operadores y es independiente del método de análisis, que solo usa el operador de análisis. Para obtener más información sobre los análisis orientados a lotes, consulta la sección Análisis orientados a lotes de este documento.

De forma predeterminada, la mayoría de los operadores se ejecutan en el método orientado a filas, que procesa los datos de una fila a la vez. Los operadores vectorizados se ejecutan con el método orientado a lotes para ayudar a mejorar la capacidad de procesamiento y el rendimiento de la ejecución. Estos operadores procesan los datos de a un bloque por vez. Cuando un operador necesita procesar muchas filas, el método de ejecución orientado a lotes suele ser más eficiente.

Método de ejecución frente al método de análisis

El método de ejecución de consultas es independiente del método de análisis de consultas. Puedes establecer uno, ambos o ninguno de estos métodos en la sugerencia de consulta.

El método de ejecución de consultas se refiere a la forma en que los operadores de consultas procesan los resultados intermedios y cómo interactúan entre sí, mientras que el método de análisis se refiere a la forma en que el operador de análisis interactúa con la capa de almacenamiento de Spanner.

Aplica el método de ejecución que usa la consulta

Para optimizar el rendimiento de las consultas, Spanner elige el método de ejecución óptimo para tu consulta en función de varias heurísticas. Google recomienda que uses este método de ejecución predeterminado. Sin embargo, puede haber situaciones en las que quieras aplicar un tipo específico de método de ejecución.

Puedes aplicar tu método de ejecución a nivel de la sentencia. La sugerencia de consulta EXECUTION_METHOD es un aviso, no una directiva. En última instancia, el optimizador de consultas decide qué método usar para cada operador individual.

Para aplicar el método de ejecución orientado a lotes a nivel de la sentencia, usa una sugerencia de sentencia en tu consulta:

GoogleSQL

@{EXECUTION_METHOD=BATCH}
SELECT *
FROM Singers;

PostgreSQL

/*@ EXECUTION_METHOD=BATCH */
SELECT *
FROM Singers;

Aunque Google no recomienda inhabilitar el método de ejecución automático establecido por Spanner, puedes inhabilitarlo y usar el método de ejecución orientado a filas para solucionar problemas, como diagnosticar la latencia.

Para inhabilitar el método de ejecución automático y aplicar el método de ejecución orientado a filas a nivel de la sentencia, usa una sugerencia de sentencia en tu consulta:

GoogleSQL

@{EXECUTION_METHOD=ROW}
SELECT *
FROM Singers;

PostgreSQL

/*@ EXECUTION_METHOD=ROW */
SELECT *
FROM Singers;

Verifica qué método de ejecución está habilitado

No todos los operadores de Spanner admiten los métodos de ejecución orientados a lotes y a filas. Para cada operador, el visualizador del plan de ejecución de consultas muestra el método de ejecución en la tarjeta del iterador. Si el método de ejecución está orientado a lotes, se muestra Lote. Si está orientada a filas, se muestra Fila. Para obtener más información sobre el visualizador, consulta Planes de ejecución de consultas.

Si los operadores de tu consulta se ejecutan con diferentes métodos de ejecución, los adaptadores de métodos de ejecución DataBlockToRowAdapter y RowToDataBlockAdapter aparecen entre los operadores para mostrar el cambio en el método de ejecución. Para obtener más información sobre estos operadores, consulta DataBlockToRowAdapter y RowToDataBlockAdapter.