Los desarrolladores suelen hacerme la misma pregunta: "Mi API es lenta. ¿Por dónde empiezo?". El reflejo suele ser actualizar el servidor o duplicar la RAM. Eso cuesta dinero y rara vez soluciona la causa raíz. En la mayoría de las aplicaciones Laravel, el cuello de botella reside en la capa de la base de datos. La elegante sintaxis del framework hace que sea fácil olvidar que cada llamada de Eloquent acaba convirtiéndose en SQL, y que el SQL es, a menudo, donde comienza el problema.

Antes de tocar cualquier configuración del servidor, revisa tus consultas de forma metódica.

Empieza con el diagnóstico adecuado

No optimices a ciegas. Reescribir consultas al azar es dar palos de ciego, y dar palos de ciego hace perder horas.

Necesitas encontrar las sentencias que consumen la mayor cantidad de tiempo total. Observa tu aplicación bajo una carga real. Laravel Telescope te ofrece una vista clara de cada consulta ejecutada durante una solicitud, con su respectivo tiempo de ejecución. Laravel Debugbar las muestra en tu navegador durante el desarrollo local para que puedas detectar anomalías de inmediato. Cuando necesites capturar problemas en producción, habilita el MySQL Slow Query Log. Este registra las sentencias que superan un umbral que tú definas, lo que lo hace ideal para encontrar sorpresas que no aparecen en conjuntos de datos pequeños. Si ejecutas algo de mayor escala, una herramienta de Application Performance Monitoring (APM) puede correlacionar los endpoints HTTP lentos con llamadas específicas a la base de datos.

Al revisar los datos, busca dos cosas: el tiempo de ejecución absoluto y la frecuencia de llamadas. Una consulta que tarda cuarenta milisegundos parece inofensiva hasta que te das cuenta de que se ejecuta dos mil veces por minuto. Un informe de tres segundos que se ejecuta una vez por hora puede importar menos que una búsqueda de medio segundo que se ejecuta en cada página. Soluciona primero los problemas de alto impacto.

Deja de pedirlo todo

SELECT * es conveniente. También es costoso. Cuando escribes Model::all() o recuperas un conjunto de resultados sin nombrar las columnas, MySQL tiene que procesar cada campo de cada fila que coincida. Eso incluye campos de texto grandes, blobs JSON y cualquier otra cosa que haya en la tabla. El conjunto de resultados crece, el uso de memoria aumenta y el tiempo dedicado a serializar la respuesta se incrementa.

Sé explícito. Si tu controlador solo necesita los campos id, name y email, solicita exactamente esos:

User::select('id', 'name', 'email')->get();

En el query builder, se aplica el mismo principio. Las cargas de datos (payloads) más pequeñas se mueven más rápido a través de la red y consumen menos RAM en tu servidor de aplicaciones. Esta es una de las mejoras más fáciles y económicas de lograr, pero es fácil pasarla por alto porque Laravel establece SELECT * como el comportamiento por defecto.

Deja que EXPLAIN guíe tus cambios

Nunca refactorices una consulta lenta sin ejecutar EXPLAIN primero. En MySQL, la palabra clave EXPLAIN muestra el plan de ejecución de la consulta. Revela exactamente cómo el optimizador pretende encontrar tus datos.

Presta atención a la columna type. Si ves ALL, MySQL está realizando un escaneo completo de la tabla (full table scan). Eso significa que está leyendo cada fila para satisfacer tu cláusula WHERE. Observa la columna key para ver si el optimizador está utilizando algún índice. Luego, comprueba la columna Extra. Si detectas Using temporary o Using filesort, MySQL está construyendo tablas intermedias o realizando una ordenación en memoria porque tu estructura actual no puede satisfacer la consulta de forma eficiente.

Ejecuta EXPLAIN en tu cliente de MySQL o utiliza una herramienta que le dé formato a la salida. Una vez que veas el plan, sabrás si el problema es la falta de un índice, un join incorrecto o un predicado que el motor no puede optimizar. Adivinar se vuelve innecesario.

Indexa con intención

Los índices son la herramienta más potente para acelerar las búsquedas, pero solo funcionan cuando coinciden con la forma en que realizas tus consultas. Sin el índice adecuado, MySQL escanea fila por fila. Eso puede parecer aceptable en desarrollo con una tabla de mil filas, pero puede colapsar en producción con una tabla de diez millones.

Empieza con índices de una sola columna para los campos que aparecen con frecuencia en las cláusulas WHERE. Si filtras constantemente por status, añade un índice en status.

Cuando una consulta filtra por varias columnas a la vez, pasa a los índices compuestos. El orden de las columnas dentro del índice es importante porque MySQL lee los índices compuestos de izquierda a derecha. Esto se conoce como la regla del prefijo más a la izquierda (leftmost prefix rule). Si tu consulta busca por user_id y luego ordena por created_at, un índice compuesto en (user_id, created_at) ayuda significativamente. Si inviertes el orden, es posible que el optimizador no utilice el índice para el filtro.

No indexes todas las columnas. Cada índice añade una carga adicional (overhead) a las inserciones, actualizaciones y eliminaciones, ya que MySQL debe mantener la estructura. Añádelos deliberadamente basándote en los patrones que hayas encontrado durante la medición.

Mantén las funciones alejadas de las columnas

Este error desactiva los índices de forma silenciosa. Cuando envuelves una columna en una función dentro de una cláusula WHERE, MySQL no puede