Esta es una consulta muy interesante. Durante su optimización, puede descubrir y comprender mucha información nueva sobre cómo funciona MySQL. No estoy seguro de tener tiempo para escribir todo en detalle de una vez, pero puedo actualizar gradualmente.
Por qué es lento
Hay básicamente dos escenarios:un rápido y un lento .
En un rápido escenario en el que está caminando en un orden predefinido sobre una tabla y probablemente al mismo tiempo obtenga rápidamente algunos datos por id para cada fila de otras tablas. En este caso, deja de caminar tan pronto como tenga suficientes filas especificadas por su cláusula LIMIT. ¿De dónde viene el pedido? Desde un índice de árbol b que tiene en la tabla o el orden de un conjunto de resultados en una subconsulta.
En un lento escenario no tiene ese orden predefinido, y MySQL tiene que poner implícitamente todos los datos en una tabla temporal, ordenar la tabla en algún campo y devolver el n filas de su cláusula LIMIT. Si alguno de los campos que coloca en esa tabla temporal es de tipo TEXTO (no VARCHAR), MySQL ni siquiera intenta mantener esa tabla en la RAM y la vacía y la ordena en el disco (por lo tanto, procesamiento de E/S adicional).
Lo primero que hay que arreglar
Hay muchas situaciones en las que no puede crear un índice que le permita seguir su orden (por ejemplo, cuando ORDENA POR columnas de diferentes tablas), por lo que la regla general en tales situaciones es minimizar los datos que MySQL colocará. en la tabla temporal. ¿Cómo puedes hacerlo? Selecciona solo los identificadores de las filas en una subconsulta y, una vez que tiene los identificadores, los une a la tabla en sí y a otras tablas para obtener el contenido. Es decir, haces una pequeña tabla con un pedido y luego usas el escenario rápido. (Esto contradice ligeramente a SQL en general, pero cada versión de SQL tiene sus propios medios para optimizar las consultas de esa manera).
Coincidentemente, su SELECT -- everything is ok here
se ve divertido, ya que es el primer lugar donde no está bien.
SELECT p.*
, u.name user_name, u.status user_status
, c.name city_name, t.name town_name, d.name dist_name
, pm.meta_name, pm.meta_email, pm.meta_phone
, (SELECT concat("{",
'"id":"', pc.id, '",',
'"content":"', replace(pc.content, '"', '\\"'), '",',
'"date":"', pc.date, '",',
'"user_id":"', pcu.id, '",',
'"user_name":"', pcu.name, '"}"') last_comment_json
FROM post_comments pc
LEFT JOIN users pcu ON (pcu.id = pc.user_id)
WHERE pc.post_id = p.id
ORDER BY pc.id DESC LIMIT 1) AS last_comment
FROM (
SELECT id
FROM posts p
WHERE p.status = 'published'
ORDER BY
(CASE WHEN p.created_at >= unix_timestamp(now() - INTERVAL p.reputation DAY)
THEN +p.reputation ELSE NULL END) DESC,
p.id DESC
LIMIT 0,10
) ids
JOIN posts p ON ids.id = p.id -- mind the join for the p data
LEFT JOIN users u ON (u.id = p.user_id)
LEFT JOIN citys c ON (c.id = p.city_id)
LEFT JOIN towns t ON (t.id = p.town_id)
LEFT JOIN dists d ON (d.id = p.dist_id)
LEFT JOIN post_metas pm ON (pm.post_id = p.id)
;
Ese es el primer paso, pero incluso ahora puede ver que no necesita hacer estos LEFT JOINS inútiles y serializaciones json para las filas que no necesita. (Omití GROUP BY p.id
, porque no veo qué LEFT JOIN podría dar como resultado varias filas, no haces ninguna agregación).
aún por escribir sobre:
- índices
- reformular la cláusula CASE (usar UNION ALL)
- probablemente forzando un índice