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:
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:
- Observa los cambios en el plan de consulta debidos a cambios en el esquema (por ejemplo, añadir o quitar un índice).
- Observar los cambios en el plan de consulta debido a una actualización de la versión del optimizador.
- Observa los cambios en el plan de consulta debidos a las nuevas estadísticas del optimizador, que se recogen automáticamente cada tres días o se realizan manualmente mediante el comando
ANALYZE
.
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:
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:
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:
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:
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:
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
Información sobre los operadores de ejecución de consultas
Consulta información sobre el optimizador de consultas de Spanner.
Consulta cómo gestionar el optimizador de consultas.