Optimización de consultas SQL

El rendimiento de una consulta en MySQL que utilice solamente JOINs versus otra que emplee subconsultas es relativo; esto depende mucho de la naturaleza del problema que necesites resolver. Puesto que la respuesta a esta incertidumbre es asociada más bien a la optimización de la consultas como un todo donde cada parte aporta un peso que puede ser revisado o expuesto con un analizador de consultas tal como hace el comando EXPLAIN de MySQL. En muchas ocasiones verás que la solución es usar una combinación de JOIN y subconsultas.

Sin embargo, en este post dejaremos lo anterior a un lado y nos enfocaremos solamente en comparar JOINs y subconsultas de manera independiente. Así podremos ver que existen algunos factores que inciden en esta comparación, describiremos en qué escenarios uno tiene ventaja sobre el otro, cómo proceder a evaluarlos hasta llegar a una conclusión.

Repaso

El comando JOIN corresponde a un operador relacional que devuelve como resultado una tabla y como tal solamente puede ir en la cláusula FROM del comando SELECT, pero al contrario de lo que muchos piensan en SQL no es obligatorio el uso de índices para su funcionamiento.

Las subconsultas son consultas dentro de otra consulta más externa o dispuestas de forma anidada, y puedes colocarlas en cualquier parte de un SELECT. Es por esto que tal vez las has visto en la lista de campos de la cláusula SELECT, en la cláusula FROM o dentro de WHERE. Las subconsultas pueden devolver tres tipos de resultados: un valor escalar, una columna o lista de valores y una tabla.

Veamos los factores que pueden incidir en el rendimiento de estos dos comandos:

El uso de índices

Este factor es el primero que salta a la vista, los índices son parte esencial de los manejadores de bases de datos como MySQL tanto es así que influyen de forma exponencial en el rendimiento de operadores como JOIN y esto le brinda una gran ventaja si la subconsulta no lo aprovecha también.

El tamaño de las tablas

Ciertamente para notar la diferencia de tiempo, si utilizas un conjunto de datos pequeño es casi seguro que ni te enteres pero entre las vicisitudes que se producen en el trabajo de campo es muy común la aparición de consultas muy lentas. En alguna ocasión tuve un proceso que tardaba como 30 minutos, en casos como estos es cuando comienzas a preocuparte por lo que afecta el rendimiento y cómo mejorarlo.

Previendo situaciones como ésta es que para una comparación entonces debes considerar escenarios con tablas adecuadas de varios tamaños. Por ejemplo uno inicial que maneje tablas de 10.000 registros, el siguiente puede ser con 100.000 registros, y después otro con un millón o varios. Aunque parecen tamaños muy grandes, recuerda que para el procesamiento de datos actual de los CPUs todavía son números muy pequeños.

El optimizador

Probablemente no sepas esto, pero tal vez estás pensando que se está ejecutando la consulta que escribiste y en realidad se está ejecutando otra equivalente que es la versión optimizada por el DBMS.

El uso de caché

Tal vez es más sorprendente, pero los resultados de las consultas también pueden almacenarse en caché y si la misma consulta con el mismo conjunto de datos es solicitada nuevamente, entonces la consulta no se ejecuta y se devuelve el resultado directamente.

Ahora que te he mencionado que hay factores que pueden engañarte fácilmente, ya estás listo para ver los casos en que una solución puede ser más ventajosa que otra.

¿En qué casos elegiría un JOIN?

En la gran mayoría de los casos los JOINs bien escritos deberían superar en rapidez a las subconsultas. Por ejemplo un JOIN es preferible en consultas que se hagan sobre tablas que busquen determinar clasificaciones de algún tipo porque generalmente las subconsultas usan el operador IN que es muy lento.

Por ejemplo, para determinar los proveedores que pertenezcan a la categoría de un tipo dado 'A', podemos usar la siguiente subconsulta:

SELECT *
FROM   providers
WHERE  category_id IN (SELECT id FROM categories WHERE type = 'A');

El campo que se compara en la subconsulta no depende de un valor de la consulta externa. La consulta probablemente se evaluaría de la forma tradicional como sigue:

  1. Primero se obtiene las categorías del tipo 'A',
  2. Luego, recorre toda la tabla de proveedores,
  3. Para filtrarla, ahora hay que comparar el id de la categoría sucesivamente con todos los ids de la lista de categorías del grupo 'A',
  4. El número de comparaciones dependerá de si es necesario llegar a comparar hasta el final de esta lista, lo cual crea un retardo adicional,
  5. Solo quedan los proveedores que obtuvieron una coincidencia con la lista,
  6. Finalmente muestra los campo de cada proveedor.

La subconsulta puede ser sustituida por una consulta que use un join:

SELECT providers.*
FROM   providers
       JOIN categories
       ON providers.category_id = category.id
WHERE  categories.type = 'A';

En este caso usa el poder de los índices para hacer el join de las dos tablas, después filtra por la categoría y devuelve solamente los campos del proveedor.

Es evidente que la descripción del segundo procedimiento fué más simple y directa para llegar al resultado y puede usar los índices.

¿Cuando la subconsulta podría ser más rápida?

Tal vez muy pocas, si empleas como parámetro el tiempo de respuesta sobre todo porque MySQL utiliza el optimizador para reescribir las subconsultas como JOINs en donde puede aplicar esa estrategia. Aunque debes tomar en cuenta que existen subconsultas que no pueden ser reescritas utilizando JOINs.

Las subconsultas colocadas en la cláusula SELECT o WHERE siempre son optimizables con JOINs pero las que se ubican en FROM son capaces de producir un tamaño de tablas derivadas más pequeño que los JOINs, como en el siguiente ejemplo:

He aquí la subconsulta:

SELECT contacts.last_name, subquery1.total_size
FROM   contacts,
       (SELECT site_name, SUM(file_size) AS total_size
        FROM pages
        GROUP BY site_name) AS subquery1
WHERE  subquery1.site_name = contacts.site_name;

He aquí su contraparte con JOINs:

SELECT   contacts.last_name, SUM(file_size) AS total_size
FROM     contacts
         JOIN pages
         ON contacts.site_name = pages.site_name
GROUP BY contacts.site_name;

En un caso de 10 contactos y 30 páginas en promedio por sitio, el resultado de la subconsulta en la cláusula FROM es el producto de 10 x 10 = 100 registros ya que GROUP BY lo garantiza. Y por otra parte, en la solución con joins la cláusula FROM produciría 10 x 30 = 300 filas.

Conclusiones

  • En la gran mayoría de las consultas los JOINs escritos de forma óptima serán más rápidos y los índice que utiliza de forma recomendada solo contribuyen a aumentar la brecha.
  • Por otra lado el fuerte de las subconsultas no son las mejoras en el tiempo de respuesta sino que te proporcionan más expresividad que los JOINs, permitiendo consultas más fáciles de entender con un menor esfuerzo. Lo que significa esto es que en la medida que el problema que abordes sea más complejo, para resolverlo con joins seguramente necesitarás más tiempo para escribir la consulta correctamente, con un código más difícil de entender, depurar y más propenso a errores.
  • Afortunadamente, la elección de uno u otro no es excluyente y de hecho lo más frecuente es ver consultas que usan JOINs mezclados con subconsultas y viceversa.

Debo recordarte que puedes medir los tiempos viendo el plan de ejecución de MySQL con el comando EXPLAIN, si te interesa ver este tema por favor avísanos en la sección de comentarios.

Para aprender más sobre MySQL puedes adquirir nuestro libro Aprende MySQL con descuento.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.