Lectura de los planes EXPLAIN de Aurora DSQL
Comprender cómo leer los planes EXPLAIN es clave para optimizar el rendimiento de las consultas. En esta sección, analizaremos ejemplos reales de planes de consultas de Aurora DSQL, mostraremos cómo se comportan los diferentes tipos de escaneo, explicaremos dónde se aplican los filtros y destacaremos las oportunidades de optimización.
Tablas de muestra utilizadas en estos ejemplos
Los ejemplos siguientes hacen referencia a dos tablas: transaction y account.
La tabla transaction o dispone de clave principal, lo que provoca que Aurora DSQL escanee toda la tabla al consultarla.
La tabla account tiene un índice en customer_id. Este índice incluye balance y status como columnas de cobertura, lo que permite responder a determinadas consultas directamente desde el índice sin necesidad de leer la tabla base. Sin embargo, el índice no incluye created_at, por lo que las consultas que hacen referencia a esta columna requieren un acceso adicional a la tabla.
CREATE TABLE transaction ( account_id uuid, transaction_date timestamp, description text ); CREATE TABLE account ( customer_id uuid, balance numeric, status varchar, created_at timestamp ); CREATE INDEX ASYNC idx1 ON account (customer_id) INCLUDE (balance, status);
Ejemplo de escaneo completo
Aurora DSQL tiene escaneos secuenciales, que funcionan de manera idéntica a PostgreSQL, así como escaneos completos. La única diferencia entre estos dos es que los escaneos completos pueden utilizar filtros adicionales en el almacenamiento. Debido a esto, casi siempre se selecciona por encima de los escaneos secuenciales. Debido a la similitud, solo trataremos ejemplos de los escaneos completos más interesantes.
Los escaneos completos se utilizarán principalmente en tablas sin clave principal. Como las claves principales de Aurora DSQL son índices de cobertura completa de forma predeterminada, lo más probable es que Aurora DSQL utilice escaneos solo indexados en la clave principal en muchas situaciones en las que PostgreSQL utilizaría un escaneo secuencial. Como ocurre con la mayoría de las demás bases de datos, una tabla sin índices se escalará mal.
EXPLAIN SELECT account_id FROM transaction WHERE transaction_date > '2025-01-01' AND description LIKE '%external%';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Full Scan (btree-table) on transaction (cost=125100.05..177933.38 rows=33333 width=16)
Filter: (description ~~ '%external%'::text)
-> Storage Scan on transaction (cost=12510.05..17793.38 rows=66666 width=16)
Projections: account_id, description
Filters: (transaction_date > '2025-01-01 00:00:00'::timestamp without time zone)
-> B-Tree Scan on transaction (cost=12510.05..17793.38 rows=100000 width=30)
Este plan muestra dos filtros aplicados en diferentes etapas. La condición transaction_date >
'2025-01-01' se aplica en la capa de almacenamiento, lo que reduce la cantidad de datos que se devuelven. La condición description LIKE '%external%' se aplica más adelante en el procesador de consultas, una vez transferidos los datos, lo que hace que sea menos eficiente. Insertar filtros más selectivos en las capas de almacenamiento o índice generalmente mejora el rendimiento.
Ejemplo de escaneo solo de índices
Los escaneos solo de índices son los tipos de escaneo más óptimos en Aurora DSQL, ya que producen el menor número de viajes de ida y vuelta a la capa de almacenamiento y son los que más filtran. Sin embargo, el hecho de que vea escaneo solo de índice no significa que tenga el mejor plan. Debido a los diferentes niveles de filtrado que se pueden producir, es esencial seguir prestando atención a los diferentes lugares en los que se puede producir el filtrado.
EXPLAIN SELECT balance FROM account WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' AND balance > 100 AND status = 'pending';
QUERY PLAN
-------------------------------------------------------------------------------
Index Only Scan using idx1 on account (cost=725.05..1025.08 rows=8 width=18)
Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
Filter: (balance > '100'::numeric)
-> Storage Scan on idx1 (cost=12510.05..17793.38 rows=9 width=16)
Projections: balance
Filters: ((status)::text = 'pending'::text)
-> B-Tree Scan on idx1 (cost=12510.05..17793.38 rows=10 width=30)
Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
En este plan, la condición de indexación (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'), se evalúa primero durante el escaneo del índice, que es la etapa más eficiente porque limita la cantidad de datos que se leen del almacenamiento. El filtro de almacenamiento, status = 'pending', se aplica después de leer los datos, pero antes de enviarlos a la capa de procesamiento, lo que reduce la cantidad de datos transferidos. Por último, el filtro del procesador de consultas, balance > 100, se ejecuta en último lugar, después de mover los datos, por lo que es el menos eficiente. De estas, la condición de índice es la que ofrece el mejor rendimiento, ya que controla directamente la cantidad de datos que se escanean.
Ejemplo de escaneo de índices
Los escaneos de índices son similares a los escaneos solo de índices, excepto que tienen el paso adicional de tener que llamar a la tabla base. Como Aurora DSQL puede especificar filtros de almacenamiento, puede hacerlo tanto en la llamada de índice como en la llamada de búsqueda.
Para que quede claro, Aurora DSQL presenta el plan como dos nodos. De esta forma, puede ver claramente en qué medida puede ayudar agregar una columna de inclusión en términos de filas devueltas desde el almacenamiento.
EXPLAIN SELECT balance FROM account WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' AND balance > 100 AND status = 'pending' AND created_at > '2025-01-01';
QUERY PLAN
----------------------------------------------------------------------------------------------------------
Index Scan using idx1 on account (cost=728.18..1132.20 rows=3 width=18)
Filter: (balance > '100'::numeric)
Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
-> Storage Scan on idx1 (cost=12510.05..17793.38 rows=8 width=16)
Projections: balance
Filters: ((status)::text = 'pending'::text)
-> B-Tree Scan on account (cost=12510.05..17793.38 rows=10 width=30)
Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
-> Storage Lookup on account (cost=12510.05..17793.38 rows=4 width=16)
Filters: (created_at > '2025-01-01 00:00:00'::timestamp without time zone)
-> B-Tree Lookup on transaction (cost=12510.05..17793.38 rows=8 width=30)
Este plan muestra cómo se filtra en varias etapas:
-
La condición de indexación en
customer_idfiltra los datos de forma temprana. -
El filtro de almacenamiento en
statusreduce aún más los resultados antes de que se envíen a informática. -
El filtro del procesador de consultas en
balancese aplica más adelante, después de la transferencia. -
El filtro de búsqueda en
created_atse evalúa al buscar columnas adicionales de la tabla base.
Agregar columnas de uso frecuente como campos de INCLUDE suele eliminar esta búsqueda y mejorar el rendimiento.
Prácticas recomendadas
-
Alinee los filtros con las columnas indexadas para acelerar el filtrado.
-
Utilice las columnas INCLUDE para permitir escaneos solo de índices y evitar búsquedas.
-
Valide las estimaciones de filas al investigar los problemas de rendimiento. Aurora DSQL administra las estadísticas automáticamente ejecutando
ANALYZEen segundo plano en función de las tasas de cambio de los datos. Si las estimaciones parecen inexactas, puede ejecutarANALYZEmanualmente para actualizar las estadísticas inmediatamente. -
Evite las consultas no indexadas en tablas grandes para evitar costosos escaneos completos.