Questa è una domanda molto interessante. Durante la sua ottimizzazione potresti scoprire e comprendere molte nuove informazioni su come funziona MySQL. Non sono sicuro di avere il tempo di scrivere tutto nei dettagli in una volta, ma posso aggiornare gradualmente.
Perché è lento
Ci sono fondamentalmente due scenari:un veloce e un lento .
In un veloce scenario stai camminando in un ordine predefinito su una tabella e probabilmente allo stesso tempo recupera rapidamente alcuni dati per ID per ogni riga da altre tabelle. In questo caso smetti di camminare non appena hai abbastanza righe specificate dalla tua clausola LIMIT. Da dove viene l'ordine? Da un indice b-tree che hai sulla tabella o dall'ordine di un set di risultati in una sottoquery.
In un lento scenario in cui non hai quell'ordine predefinito e MySQL deve inserire implicitamente tutti i dati in una tabella temporanea, ordinare la tabella su un campo e restituire n righe dalla tua clausola LIMIT. Se uno qualsiasi dei campi inseriti in quella tabella temporanea è di tipo TEXT (non VARCHAR), MySQL non tenta nemmeno di mantenere quella tabella nella RAM e la svuota e la ordina su disco (quindi elaborazione IO aggiuntiva).
Prima cosa da sistemare
Ci sono molte situazioni in cui non puoi costruire un indice che ti permetta di seguirne l'ordine (quando ordini per colonne da tabelle diverse, ad esempio), quindi la regola pratica in tali situazioni è ridurre al minimo i dati che MySQL metterà nella tabella temporanea. Come si può fare? Seleziona solo gli identificatori delle righe in una sottoquery e dopo aver ottenuto gli ID, unisci gli ID alla tabella stessa e ad altre tabelle per recuperare il contenuto. Cioè si crea un piccolo tavolo con un ordine e quindi si utilizza lo scenario rapido. (Questo è leggermente in contraddizione con SQL in generale, ma ogni versione di SQL ha i suoi mezzi per ottimizzare le query in questo modo).
Per coincidenza, il tuo SELECT -- everything is ok here
sembra divertente, dato che è il primo posto in cui non va bene.
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)
;
Questo è il primo passo, ma anche ora puoi vedere che non è necessario creare questi inutili LEFT JOINS e serializzazioni json per le righe che non ti servono. (Ho saltato GROUP BY p.id
, poiché non vedo quale LEFT JOIN potrebbe comportare più righe, non esegui alcuna aggregazione).
ancora di cui scrivere:
- indici
- riformulare la clausola CASE (usare UNION ALL)
- probabilmente forzando un indice