Planes de ejecución de consultas

Introducción

En esta página, se proporcionan conceptos sobre los planes de ejecución de consultas y cómo Cloud Spanner los usa para realizar consultas en un entorno distribuido. Si deseas obtener información sobre cómo recuperar un plan de ejecución para una consulta específica con Cloud Console, visita la sección sobre cómo Cloud Spanner ejecuta las consultas.

Cloud Spanner usa instrucciones de SQL declarativas para consultar sus bases de datos. Las instrucciones de 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 para saber cómo se obtuvieron los resultados. Para una instrucción de SQL determinada, puede haber varias formas de obtener los resultados. El optimizador de consultas de Cloud Spanner evalúa los planes de ejecución diferentes y selecciona el que considera más eficaz. A continuación, Cloud Spanner usa el plan de ejecución para recuperar los resultados.

De forma conceptual, un plan de ejecución es un árbol de operadores relacionales. Cada operador lee filas de sus entradas y produce filas de salida. El resultado del operador en la raíz de la ejecución se muestra como resultado de la consulta de SQL.

Por ejemplo:

SELECT s.SongName FROM Songs AS s;

Mediante la consulta anterior, se obtiene como resultado un plan de ejecución de consultas que se puede visualizar de la siguiente manera:

Ejemplo del plan de ejecución 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");

Obtener planes de ejecución eficaces es un desafío, ya que Cloud Spanner separa los datos en divisiones. Las divisiones se pueden mover de forma independiente unas de otras y pueden asignarse a servidores distintos, que pueden estar en diferentes ubicaciones físicas. Para evaluar los planes de ejecución sobre los datos distribuidos, Cloud Spanner usa la ejecución en función de lo siguiente:

  • la ejecución local de subplanes en servidores que contienen los datos
  • la organización y agregación de varias ejecuciones remotas con una fuerte reducción de la distribución

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

El ciclo de una consulta

Una consulta de SQL en Cloud Spanner se compila primero en un plan de ejecución y, luego, se envía a un servidor raíz inicial para su ejecución. El servidor raíz se elige para minimizar la cantidad de saltos con el fin de llegar a los datos que se consultan. El servidor raíz realiza lo siguiente:

  • inicia la ejecución remota de los subplanes (si es necesario)
  • espera los resultados de las ejecuciones remotas
  • maneja los pasos de ejecución local restantes, como la agregación de resultados
  • muestra los resultados de la consulta

Los servidores remotos que reciben un subplan actúan como un servidor “raíz” para su subplan y siguen el mismo modelo que el servidor raíz superior. El resultado es un árbol de ejecuciones remotas. De forma conceptual, la ejecución de la consulta fluye de arriba abajo y los resultados de la consulta se muestran 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 agregadas

Una consulta agregada implementa consultas GROUP BY.

Por ejemplo:

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

Los resultados de la consulta anterior son los siguientes:

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

De manera conceptual, este es el plan de ejecución:

Plan de ejecución de consultas agregadas

Cloud 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 comienza con una unión distribuida, que distribuye los subplanes a los servidores remotos con divisiones que cumplen con SingerId < 100. Las uniones distribuidas locales, que se muestran más adelante en el plan, representan la ejecución en los servidores remotos. Cada unión distribuida local evalúa una subconsulta de forma independiente en las divisiones de la tabla Songs, sujeta al filtro SingerId < 100. Las uniones distribuidas locales muestran los resultados a un operador de agregación. El operador de agregación realiza la agregación COUNT en SingerId y muestra los resultados a un operador de serialización de resultados. El operador de serialización de resultados serializa los resultados en filas que contienen el recuento de canciones por SingerId. La unión distribuida unifica todos los resultados y muestra los resultados de la consulta.

Para obtener más información sobre los agregados, consulta la página sobre el operador de agregación.

Consultas de unión ubicadas en el mismo lugar

Las tablas intercaladas se almacenan de forma física con sus filas de tablas relacionadas ubicadas en el mismo lugar. Una unión ubicada en el mismo lugar es una unión entre tablas intercaladas. Las uniones ubicadas en el mismo lugar pueden ofrecer beneficios de rendimiento en comparación con las uniones que requieren índices o reversión de uniones.

Por ejemplo:

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 da por sentado que Songs está intercalado en Albums).

Los resultados de la consulta anterior son los siguientes:

+-----------------------+--------------------------+
| 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 uniones ubicadas en el mismo lugar

Este plan de ejecución comienza con una unión distribuida, que distribuye los subplanes a los 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 unirse a un servidor diferente.

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

Consultas de índices y de reversiones de uniones

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

Considera un índice creado con el siguiente comando:

CREATE INDEX SongsBySongName ON Songs(SongName)

Usa este índice en la siguiente consulta:

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

Los resultados de la consulta anterior son los siguientes:

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

Este es el plan de ejecución:

Plan de ejecución de consultas de reversiones de uniones

El plan de ejecución resultante es complicado porque el índice SongsBySongName no contiene la columna Duration. Para obtener el valor Duration, Cloud Spanner necesita revertir la unión de los resultados indexados a la tabla Songs. Esta es una unión, pero no se ubica en un mismo lugar 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 unión ubicada en el mismo lugar, porque Cloud Spanner realiza optimizaciones para acelerar la ejecución si los datos no están ubicados en el mismo lugar.

El operador principal es aplicación distribuida cruzada. La entrada de este operador son lotes de filas del índice SongsBySongName que cumplen con el predicado STARTS_WITH(s.SongName, "B"). La aplicación distribuida cruzada asigna los lotes a los servidores remotos, que tienen divisiones con los datos Duration. Los servidores remotos usan un análisis de tabla para recuperar la columna Duration. El análisis de tabla usa el filtro Condition:($Songs_key_TrackId' = $batched_Songs_key_TrackId), que une TrackId de la tabla Songs a TrackId de las filas que se agruparon por lote del índice SongsBySongName.

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

Considera hacer una consulta un poco 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 como se muestra en este plan de ejecución:

Plan de ejecución de consultas más simple

El plan de ejecución no necesita una reversión de la unión, porque todas las columnas que solicita la consulta están presentes en el índice.

Próximos pasos