Optimizar dos consultas de pedidos

MySQLBeginner
Practicar Ahora

Introducción

Una tienda en crecimiento necesita dos vistas útiles de pedidos: los pedidos listos más antiguos de una tienda y el historial más reciente de un cliente. La primera página contiene siete pedidos. Los ID de pedido distintos deben seguir separados aunque coincidan las marcas temporales y los precios; un pedido promocional de cero centavos también puede estar listo para recoger.

Guardarás dos consultas MySQL reutilizables y un script de índices, aplicarás índices útiles y mantendrás ambas primeras páginas dentro de un presupuesto compartido de dos índices secundarios y 64 solicitudes nativas de filas de diagnóstico por página. La instantánea sintética original tiene más de 96,000 pedidos. Esta unidad optimiza primeras páginas; la siguiente unidad independiente enseña paginación completa con cursores.

Necesitas conocimientos básicos para moverte por la terminal, sobre conexiones MySQL y sobre SELECT/WHERE. Los parámetros, los intervalos temporales semiabiertos, la ordenación, los índices y las observaciones de planes reales se explican antes de usarlos. El servidor MySQL privado, los datos originales y Terminal están preparados; no se requiere desarrollo en Python ni de interfaces web.

Trabaja en /home/labex/project/order-desk. Crea o edita queue.sql, history.sql y indexes.sql, y gestiona como máximo dos índices secundarios. Conserva los pedidos originales, las columnas, la clave primaria, las identidades, los estados, las marcas temporales exactas de la aplicación, los centavos, las notas y las herramientas ejecutables. No borres ni reescribas filas fuente, cambies la configuración de diagnóstico, añadas indicaciones al optimizador ni uses una caché externa. Los nombres de índice y el SQL equivalente pueden variar. Las etiquetas y salidas del entorno se mantienen en inglés.

Las conexiones de diagnóstico preparadas desactivan index condition pushdown, una optimización que puede ocultar el filtrado dentro del motor de almacenamiento. Esto facilita la interpretación de las comparaciones de solicitudes nativas de filas. Se aplica solo a estas conexiones; el ejercicio no mide E/S física de disco ni promete un factor de mejora de velocidad en producción. Los milisegundos y una cadena exacta de plan no son objetivos de aceptación.

Examinar la carga de trabajo y optimizar la cola de recogida

En este paso, inspeccionarás pedidos válidos antes de elegir un índice útil. Una primera página más rápida debe seguir conteniendo los mismos datos de pedido requeridos.

Empieza en Terminal, en el intérprete normal del sistema. cd entra en el espacio de trabajo; ./ ejecuta un programa del directorio actual. db abre el cliente MySQL preparado para leer pedidos y gestionar índices del curso. Un here-document envía las líneas entre el marcador SQL entre comillas y su SQL de cierre a ese cliente. El marcador de cierre aparece solo. Las instrucciones SQL terminan con punto y coma y esta conexión de cliente se cierra al finalizar el bloque. Si escribes mal una ruta o un comando, usa ls para inspeccionar los nombres reales y vuelve a intentar el comando corregido; conserva intactos los datos originales.

Consulta los pedidos listos de la tienda 3 del 10 de mayo. La comparación >= incluye la apertura; < excluye la medianoche siguiente. ORDER BY placed_at,order_id ordena por tiempo y después por ID, de forma ascendente. El ID resuelve los empates de marca temporal. total_cents almacena centavos enteros y placed_at es un DATETIME(6): una marca temporal de aplicación que conserva seis dígitos de microsegundos. Estas marcas temporales proporcionadas no requieren conversión de zona horaria.

cd /home/labex/project/order-desk
./db <<'SQL'
SELECT order_id,shop_id,customer_id,state,placed_at,total_cents
FROM orders
WHERE shop_id=3 AND state='ready'
  AND placed_at>='2025-05-10' AND placed_at<'2025-05-11'
ORDER BY placed_at,order_id;
SQL

Hay diez pedidos que cumplen los requisitos: nueve ID distintos comparten 09:15 y el pedido 900021 ocurre a las 23:59:59.999999. El pedido 900004 cuesta cero y sigue listo. La mayoría de los pedidos de la misma hora cuestan 1250 centavos, pero un precio igual no es una identidad. La medianoche siguiente exacta pertenece al día siguiente. Conserva estos datos mientras cambias las rutas de acceso.

Un índice es una estructura ordenada adicional que ayuda a MySQL a localizar filas. Consume almacenamiento y requiere mantenimiento al escribir, por lo que el objetivo no es añadir todos los índices posibles. Un índice compuesto ordena las entradas por varias columnas. La clave primaria ya identifica order_id; este puesto permite como máximo dos índices secundarios adicionales entre ambas cargas de trabajo.

Un plan describe cómo MySQL llega a un resultado. EXPLAIN ordinario incluye estimaciones. EXPLAIN ANALYZE ejecuta realmente el SELECT e informa de observaciones de iteradores como actual ... rows=... loops=.... FORMAT=TREE muestra los pasos de ejecución, llamados iteradores, como texto padre/hijo con sangría. LIMIT 7 solicita como máximo siete filas ordenadas. Ese iterador superior puede devolver siete filas mientras un iterador inferior de recorrido de tabla lee un conjunto mucho mayor. Sus tiempos transcurridos varían y no se puntúan. DATE(placed_at) toma la fecha de calendario, por lo que esta solicitud de referencia concreta sigue seleccionando el día único correcto.

./db <<'SQL'
EXPLAIN ANALYZE FORMAT=TREE
SELECT order_id,shop_id,customer_id,state,placed_at,total_cents
FROM orders
WHERE shop_id=3 AND state='ready' AND DATE(placed_at)='2025-05-10'
ORDER BY placed_at,order_id
LIMIT 7;
SQL

Lee el iterador inferior de acceso, no solo las siete filas de arriba. Sin un índice adecuado recorre unos 96,000 pedidos. La conexión de diagnóstico mantiene desactivado index condition pushdown para que el filtrado siga visible; conserva esa configuración preparada de comparación. Una página pequeña devuelta no demuestra por sí sola poco trabajo de base de datos.

Guarda una consulta que gestione distintas solicitudes de tienda y estado e intervalos temporales semiabiertos arbitrarios. Anteponer @ a un nombre lo convierte en una variable de sesión MySQL nativa. El puesto proporciona @group_id, @state, @from_at y @until_at para cada solicitud en la conexión de esa consulta. Una variable establecida en una conexión manual de db no persiste en otra. Mantén las instrucciones SET fuera del archivo de consulta guardado.

Las dos interfaces de consulta devuelven seis columnas en este orden: order_id, group_id, actor_id, state, event_at, amount_cents. AS da a una columna seleccionada el nombre de interfaz: la tienda es el grupo y el cliente es el actor. Conserva los valores reales en lugar de reemplazarlos por resúmenes. LIMIT 7 es el contrato de página de siete pedidos del puesto; reducirlo ocultaría pedidos en lugar de optimizar la página. ASC hace explícito el orden ascendente.

cat > queue.sql escribe el texto completo siguiente en ese archivo y reemplaza cualquier contenido anterior. El marcador de here-document entre comillas impide la expansión del intérprete. Pega el bloque entero, con el marcador de cierre solo:

cat > queue.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
       state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE shop_id=@group_id AND state=@state
  AND placed_at>=@from_at AND placed_at<@until_at
ORDER BY placed_at ASC,order_id ASC
LIMIT 7;
SQL
./desk page queue

page ejecuta tu SQL guardado sin cambios y muestra sus filas reales. La primera página contiene los ID de 900001 a 900007, incluido el pedido de cero centavos. El valor nativo handler_requests cuenta solicitudes en el límite entre servidor y motor de almacenamiento en esta conexión de diagnóstico; no es un recuento de E/S física. Sigue siendo grande antes de indexar. El cursor mostrado para la siguiente solicitud procede únicamente de la última fila real devuelta; esta consulta de primera página todavía no lo usa. Las entradas de cursor mostradas como null significan que no hay una fila anterior en una solicitud de primera página.

Un prefijo de acceso útil fija primero la tienda y el estado, y después sigue el intervalo temporal y el orden (time,ID) solicitados. Las dos columnas de igualdad pueden intercambiar posiciones para estas consultas; su orden textual en WHERE no impone el orden del índice. Un índice solo de tiempo puede seguir examinando tiendas o estados ajenos en otras solicitudes. Guarda el candidato seleccionado en indexes.sql para poder aplicarlo también a una copia nueva.

cat > indexes.sql <<'SQL'
CREATE INDEX queue_time ON orders(shop_id,state,placed_at,order_id);
SQL
./db < indexes.sql

< proporciona un archivo como entrada SQL del cliente. Esto crea una estructura de acceso, no pedidos nuevos. Si después vuelves a ejecutar el mismo CREATE INDEX, MySQL informa de un nombre de clave duplicado porque ese índice ya existe. Inspecciona los índices existentes antes de decidir si conservar o reemplazar un candidato creado para el curso; no borres pedidos fuente ni esperes que este script sea una migración idempotente.

Aquí -e ejecuta la cadena SQL completa entre comillas y sale. La acción plan del puesto ejecuta EXPLAIN ANALYZE sobre la consulta guardada real. --from y --until seleccionan otra apertura y otro final exclusivo para esta solicitud; los valores de solo fecha significan medianoche.

./db -e "SHOW INDEX FROM orders;"
./desk page queue
./desk plan queue
./desk page queue --from 2025-01-05 --until 2025-01-12

La ruta de acceso debería usar ahora un intervalo indexado acotado, mientras la página original del 10 de mayo sigue conteniendo los mismos siete ID de pedido y campos exactos. Comprueba ambas solicitudes frente al presupuesto de 64 solicitudes. SHOW INDEX demuestra la existencia; el plan real, las filas devueltas y el recuento de solicitudes establecen el comportamiento útil. Los nombres exactos de índice, los costes y los milisegundos pueden variar.

Si escribes mal un comando o nombre de archivo, lee el error, usa ls para inspeccionar el espacio de trabajo y vuelve a intentarlo con el nombre corregido. Order desk error significa un error de archivo, SQL o conexión; no es un resultado vacío correcto. Corrige el SQL guardado o el índice candidato y vuelve a ejecutar la solicitud real. Ejecuta la comprobación del paso después de confirmar tanto la corrección como el trabajo observado.

Compartir el presupuesto de índices con el historial del cliente

En este paso, darás soporte a una segunda solicitud de negocio sin perder la cola de recogida ni superar dos índices secundarios.

Permanece en Terminal y en /home/labex/project/order-desk. Conserva las tablas originales y queue.sql. La vista de historial selecciona un cliente, independientemente del estado de recogida o la tienda, y muestra primero los pedidos más recientes. DESC invierte la ordenación; úsalo tanto para la marca temporal como para el ID para que los pedidos de la misma hora tengan un orden determinista del más reciente al más antiguo. El parámetro nativo @actor_id identifica al cliente. No lo reemplaces por un cliente fijo ni añadas la restricción de estado de la cola.

Escribe la consulta completa de primera página del historial. Usa la misma interfaz de seis columnas y el mismo intervalo semiabierto que la consulta de recogida:

cat > history.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
       state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE customer_id=@actor_id
  AND placed_at>=@from_at AND placed_at<@until_at
ORDER BY placed_at DESC,order_id DESC
LIMIT 7;
SQL
./desk page history
./desk plan history

El historial del 10 de mayo empieza con el pedido 900021 en el último microsegundo, seguido de los ID de la misma hora en orden descendente. Sus centavos y estados reales siguen visibles. Esta primera página correcta aún puede requerir demasiado trabajo: el índice de cola existente empieza por tienda y estado, que el historial no restringe.

Un segundo prefijo útil empieza por cliente y después por tiempo e ID. Un índice ascendente puede recorrerse hacia atrás para el orden descendente del historial; el plan observado muestra lo que realmente ocurre. Esta tarea no requiere columnas adicionales para un índice de cobertura. Mantén como máximo dos índices secundarios y conserva acceso útil para ambas cargas de trabajo.

cat >> indexes.sql añade la instrucción nueva al final del archivo fuente existente; > lo reemplazaría. Después de añadirla, ejecuta solo la instrucción CREATE nueva siguiente, porque el índice de cola ya existe en esta VM. En una base de datos nueva, el archivo guardado de dos instrucciones es la fuente completa de los índices.

cat >> indexes.sql <<'SQL'
CREATE INDEX actor_time ON orders(customer_id,placed_at,order_id);
SQL
./db -e "CREATE INDEX actor_time ON orders(customer_id,placed_at,order_id);"
./db -e "SHOW INDEX FROM orders;"
./desk page history
./desk plan history

Comprueba un historial de cliente más amplio, otra identidad, un intervalo de fechas vacío y la cola original. --actor cambia el ID de cliente solicitado; no modifica los datos fuente. Una consulta válida puede no devolver filas, lo que difiere de un error de ejecución.

./desk page history --from 2025-01-01 --until 2025-05-01
./desk page history --actor 78
./desk page history --from 2027-01-01 --until 2027-01-02
./desk page queue

Ambas cargas de trabajo deben conservar primeras páginas correctas dentro del presupuesto de diagnóstico. La solicitud vacía devuelve realmente una lista vacía de filas, no pedidos inventados. El indexes.sql guardado y las definiciones activas se comprueban de forma independiente: un archivo correcto no demuestra que se haya aplicado y un índice activo que funcione no reemplaza la fuente guardada.

Si cambias un candidato, inspecciona SHOW INDEX, elimina solo el índice creado para el curso que estás reemplazando, actualiza indexes.sql y aplica la nueva definición. Nunca elimines la clave primaria original, columnas ni filas. El error de nombre duplicado de un CREATE repetido es motivo para inspeccionar la definición actual, no para reiniciar la VM.

El objetivo final de esta unidad son dos consultas útiles de primera página. Siete filas devueltas no son una cola más larga completa. La siguiente unidad independiente comienza con estas consultas de primera página e índices ya preparados y después enseña condiciones de cursor que conservan todos los pedidos entre páginas. Ejecuta la comprobación final con ambas consultas guardadas, el script completo de índices y como máximo dos índices secundarios efectivos.

Resumen

Inspeccionaste datos reales de pedidos y observaciones de ejecución reales, guardaste consultas reutilizables de recogida e historial de cliente con intervalos semiabiertos y sustentaste ambas primeras páginas con un conjunto limitado de índices útiles. Las identidades, las marcas temporales, los centavos y las notas originales permanecen intactos. Distinguiste las solicitudes nativas de filas de diagnóstico de los recuentos de filas devueltas, las estimaciones y la E/S física. La entrega completa de varias páginas es el objetivo de la siguiente unidad.