C'est une requête très intéressante. Au cours de son optimisation, vous pourrez découvrir et comprendre de nombreuses nouvelles informations sur le fonctionnement de MySQL. Je ne suis pas sûr d'avoir le temps de tout écrire en détail d'un coup, mais je peux mettre à jour progressivement.
Pourquoi c'est lent
Il existe essentiellement deux scénarios :un rapide et un lent .
En un rapide scénario, vous marchez dans un ordre prédéfini sur une table et probablement en même temps récupérez rapidement des données par identifiant pour chaque ligne à partir d'autres tables. Dans ce cas, vous arrêtez de marcher dès que vous avez suffisamment de lignes spécifiées par votre clause LIMIT. D'où vient la commande ? À partir d'un index b-tree que vous avez sur la table ou de l'ordre d'un jeu de résultats dans une sous-requête.
Dans un lent scénario, vous n'avez pas cet ordre prédéfini, et MySQL doit implicitement mettre toutes les données dans une table temporaire, trier la table sur un champ et renvoyer le n lignes de votre clause LIMIT. Si l'un des champs que vous mettez dans cette table temporaire est de type TEXT (et non VARCHAR), MySQL n'essaie même pas de conserver cette table dans la RAM et la vide et la trie sur le disque (d'où un traitement d'E/S supplémentaire).
Première chose à corriger
Il existe de nombreuses situations où vous ne pouvez pas construire un index qui vous permettra de suivre son ordre (lorsque vous ORDER BY des colonnes de différentes tables, par exemple), donc la règle d'or dans de telles situations est de minimiser les données que MySQL mettra dans la table temporaire. Comment peux-tu le faire? Vous sélectionnez uniquement les identifiants des lignes dans une sous-requête et une fois que vous avez les identifiants, vous joignez les identifiants à la table elle-même et à d'autres tables pour récupérer le contenu. C'est-à-dire que vous créez un petit tableau avec une commande, puis utilisez le scénario rapide. (Cela contredit légèrement SQL en général, mais chaque type de SQL a ses propres moyens pour optimiser les requêtes de cette façon).
Par coïncidence, votre SELECT -- everything is ok here
ça a l'air drôle, puisque c'est le premier endroit où ça ne va pas.
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)
;
C'est la première étape, mais même maintenant, vous pouvez voir que vous n'avez pas besoin de créer ces LEFT JOINS inutiles et ces sérialisations json pour les lignes dont vous n'avez pas besoin. (J'ai ignoré GROUP BY p.id
, parce que je ne vois pas quel LEFT JOIN pourrait résulter en plusieurs lignes, vous ne faites aucune agrégation).
encore à écrire :
- index
- reformuler la clause CASE (utiliser UNION ALL)
- forçant probablement un index