Planes de ejecución de consultas

Información general

En esta página se explican los conceptos sobre los planes de ejecución de consultas y cómo los usa Spanner para realizar consultas en un entorno distribuido. Para saber cómo obtener un plan de ejecución de una consulta específica mediante la consolaGoogle Cloud , consulta Entender cómo ejecuta Spanner las consultas. También puede ver planes de consulta históricos muestreados y comparar el rendimiento de una consulta a lo largo del tiempo para determinadas consultas. Para obtener más información, consulta Ejemplos de planes de consultas.

Spanner usa instrucciones SQL declarativas para consultar sus bases de datos. Las instrucciones SQL definen qué quiere el usuario sin especificar cómo obtener los resultados. Un plan de ejecución de consultas es el conjunto de pasos que se siguen para obtener los resultados. En una instrucción SQL determinada, puede haber varias formas de obtener los resultados. El optimizador de consultas de Spanner evalúa diferentes planes de ejecución y elige el que considera más eficiente. A continuación, Spanner usa el plan de ejecución para obtener los resultados.

Conceptualmente, un plan de ejecución es un árbol de operadores relacionales. Cada operador lee filas de sus entradas y genera filas de salida. El resultado del operador de la raíz de la ejecución se devuelve como resultado de la consulta SQL.

Por ejemplo, esta consulta:

SELECT s.SongName FROM Songs AS s;

da como resultado un plan de ejecución de consultas que se puede visualizar de la siguiente manera:

Ejemplo de plan de ejecución de consultas

Las consultas y los planes de ejecución de 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 instrucciones del lenguaje de manipulación de datos (DML) para añadir 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");

Obtener planes de ejecución eficientes es complicado porque Spanner divide los datos en divisiones. Las divisiones pueden moverse de forma independiente entre sí y asignarse a servidores diferentes, que podrían estar en ubicaciones físicas distintas. Para evaluar los planes de ejecución de los datos distribuidos, Spanner usa la ejecución basada en lo siguiente:

  • ejecución local de subplanes en servidores que contienen los datos
  • orquestación y agregación de varias ejecuciones remotas con poda de distribución agresiva

Spanner usa el operador primitivo distributed union, junto con sus variantes distributed cross apply y distributed outer apply, para habilitar este modelo.

Planes de consulta muestreados

Los planes de consulta muestreados de Spanner te permiten ver muestras de planes de consulta históricos y comparar el rendimiento de una consulta a lo largo del tiempo. No todas las consultas tienen planes de consulta muestreados disponibles. Solo se pueden muestrear las consultas que consuman más CPU. Los datos de los ejemplos de planes de consulta de Spanner se conservan durante 30 días. Puedes encontrar ejemplos de planes de consultas en la página Estadísticas de consultas de la consola Google Cloud . Para obtener instrucciones, consulta Ver planes de consulta muestreados.

La estructura de un plan de consultas muestreado es la misma que la de un plan de ejecución de consultas normal. Para obtener más información sobre cómo interpretar los planes visuales y usarlos para depurar tus consultas, consulta Recorrido por el visualizador de planes de consultas.

Casos prácticos habituales de los planes de consulta muestreados:

Estos son algunos de los casos prácticos habituales de los planes de consulta muestreados:

Si el rendimiento de una consulta muestra una diferencia significativa a lo largo del tiempo o quieres mejorar el rendimiento de una consulta, consulta las prácticas recomendadas de SQL para crear instrucciones de consulta optimizadas que ayuden a Spanner a encontrar planes de ejecución eficientes.

Ciclo de vida de una consulta

Una consulta de SQL en Spanner se compila primero en un plan de ejecución y, a continuación, se envía a un servidor raíz inicial para su ejecución. El servidor raíz se elige de forma que se minimice el número de saltos para acceder a los datos consultados. El servidor raíz hace lo siguiente:

  • Inicia la ejecución remota de los subplanes (si es necesario).
  • Espera los resultados de las ejecuciones remotas.
  • Gestiona los pasos de ejecución local restantes, como la agregación de resultados.
  • Devuelve los resultados de la consulta.

Los servidores remotos que reciben un subplan actúan como servidores raíz de su subplan, siguiendo el mismo modelo que el servidor raíz de nivel superior. El resultado es un árbol de ejecuciones remotas. Conceptualmente, la ejecución de las consultas fluye de arriba abajo y los resultados de las consultas se devuelven de abajo arriba.En el siguiente diagrama se muestra este patrón:

Plan de consultas conceptual

En los siguientes ejemplos se ilustra este patrón con más detalle.

Consultas de agregación

Una consulta agregada implementa consultas GROUP BY.

Por ejemplo, con esta consulta:

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
WHERE s.SingerId < 100
GROUP BY s.SingerId;

Estos son los resultados:

+----------+-----------+
| SingerId | SongCount |
+----------+-----------+
|        3 |         1 |
|        2 |         8 |
+----------+-----------+

Conceptualmente, este es el plan de ejecución:

Plan de ejecución de consultas de agregación

Spanner envía el plan de ejecución a un servidor raíz que coordina la ejecución de la consulta y realiza la distribución remota de los subplanes.

Este plan de ejecución empieza con una unión distribuida, que distribuye subplanes a servidores remotos cuyas divisiones cumplen SingerId < 100. Una vez que se completa el análisis de las divisiones individuales, el operador agregado de la secuencia agrega las filas para obtener los recuentos de cada SingerId. A continuación, el operador serializar resultado serializa el resultado. Por último, la unión distribuida combina todos los resultados y devuelve los resultados de la consulta.

Puedes consultar más información sobre los agregados en el artículo sobre el operador de agregado.

Consultas de unión con datos colocados

Las tablas entrelazadas se almacenan físicamente con sus filas de tablas relacionadas en la misma ubicación. Una unión colocada es una unión entre tablas intercaladas. Las combinaciones colocalizadas pueden ofrecer ventajas de rendimiento con respecto a las combinaciones que requieren índices o combinaciones inversas.

Por ejemplo, con esta consulta:

SELECT al.AlbumTitle, so.SongName
FROM Albums AS al, Songs AS so
WHERE al.SingerId = so.SingerId AND al.AlbumId = so.AlbumId;

(En esta consulta se presupone que Songs está intercalado en Albums).

Estos son los resultados:

+-----------------------+--------------------------+
| AlbumTitle            | SongName                 |
+-----------------------+--------------------------+
| Nothing To Do With Me | Not About The Guitar     |
| Green                 | The Second Time          |
| Green                 | Starting Again           |
| Green                 | Nothing Is The Same      |
| Green                 | Let's Get Back Together  |
| Green                 | I Knew You Were Magic    |
| Green                 | Blue                     |
| Green                 | 42                       |
| Terrified             | Fight Story              |
+-----------------------+--------------------------+

Este es el plan de ejecución:

Plan de ejecución de consultas de unión colocada

Este plan de ejecución empieza con una unión distribuida, que distribuye subplanes a servidores remotos que tienen divisiones de la tabla Albums. Como Songs es una tabla intercalada de Albums, cada servidor remoto puede ejecutar todo el subplan en cada servidor remoto sin necesidad de combinarlo con otro servidor.

Los subplanes contienen una aplicación cruzada. Cada aplicación cruzada realiza un análisis de tabla en la tabla Albums para obtener SingerId, AlbumId y AlbumTitle. A continuación, la aplicación cruzada asigna la salida del análisis de la tabla a la salida de un análisis de índice en el índice SongsBySingerAlbumSongNameDesc, sujeto a un filtro del SingerId del índice que coincida con el SingerId de la salida del análisis de la tabla. Cada aplicación cruzada envía sus resultados a un operador serializar resultado, que serializa los datos AlbumTitle y SongName y devuelve los resultados a las uniones distribuidas locales. La unión distribuida agrega los resultados de las uniones distribuidas locales y los devuelve como resultado de la consulta.

Consultas de unión con índice y retroceso

En el ejemplo anterior se ha usado una combinación de dos tablas, una intercalada en la otra. Los planes de ejecución son más complejos y menos eficientes cuando dos tablas, o una tabla y un índice, no están intercalados.

Supongamos que se ha creado un índice con el siguiente comando:

CREATE INDEX SongsBySongName ON Songs(SongName)

Usa este índice en esta consulta:

SELECT s.SongName, s.Duration
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

Estos son los resultados:

+----------+----------+
| SongName | Duration |
+----------+----------+
| Blue     |      238 |
+----------+----------+

Este es el plan de ejecución:

Plan de ejecución de consultas de combinación hacia atrás

El plan de ejecución resultante es complicado porque el índice SongsBySongName no contiene la columna Duration. Para obtener el valor de Duration, Spanner debe volver a combinar los resultados indexados con la tabla Songs. Se trata de una combinación, pero no está colocada en el mismo sitio porque la tabla Songs y el índice global SongsBySongName no están intercalados. El plan de ejecución resultante es más complejo que el ejemplo de combinación colocada porque Spanner realiza optimizaciones para acelerar la ejecución si los datos no están colocados.

El operador superior es un cross apply distribuido. Este lado de entrada de este operador son lotes de filas del índice SongsBySongName que cumplen el predicado STARTS_WITH(s.SongName, "B"). A continuación, el operador CROSS APPLY distribuido asigna estos lotes a servidores remotos cuyas divisiones contienen los Duration datos. Los servidores remotos usan un examen de tabla para recuperar la columna Duration. El análisis de la tabla usa el filtro Condition:($Songs_key_TrackId' = $batched_Songs_key_TrackId), que une TrackId de la tabla Songs con TrackId de las filas que se agruparon del índice SongsBySongName.

Los resultados se agregan en la respuesta final de la consulta. A su vez, el lado de entrada de la aplicación cruzada distribuida contiene un par de unión distribuida o unión distribuida local para evaluar las filas del índice que cumplen el predicado STARTS_WITH.

Prueba con una consulta ligeramente diferente que no seleccione la columna s.Duration:

SELECT s.SongName
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

Esta consulta puede aprovechar al máximo el índice, tal como se muestra en este plan de ejecución:

Plan de ejecución de consultas más sencillo

El plan de ejecución no requiere una combinación hacia atrás porque todas las columnas solicitadas por la consulta están presentes en el índice.

Siguientes pasos