Dit is een zeer interessante vraag. Tijdens de optimalisatie kunt u veel nieuwe informatie ontdekken en begrijpen over hoe MySQL werkt. Ik weet niet zeker of ik de tijd zal hebben om alles in één keer in detail te schrijven, maar ik kan het geleidelijk bijwerken.
Waarom het traag is
Er zijn in principe twee scenario's:een snel en een langzame .
In een snel scenario loopt u in een vooraf gedefinieerde volgorde over een tafel en haalt u waarschijnlijk tegelijkertijd snel wat gegevens op per id voor elke rij uit andere tabellen. In dit geval stop je met lopen zodra je genoeg rijen hebt gespecificeerd door je LIMIT-clausule. Waar komt de bestelling vandaan? Van een b-tree-index die u op de tafel heeft of de volgorde van een resultaatset in een subquery.
In een traag scenario heb je die vooraf gedefinieerde volgorde niet, en MySQL moet impliciet alle gegevens in een tijdelijke tabel plaatsen, de tabel op een veld sorteren en de n teruggeven rijen uit uw LIMIT-clausule. Als een van de velden die u in die tijdelijke tabel plaatst van het type TEXT (niet VARCHAR) is, probeert MySQL die tabel niet eens in het RAM-geheugen te houden en leegt en sorteert deze op schijf (vandaar extra IO-verwerking).
Eerst op te lossen
Er zijn veel situaties waarin u geen index kunt bouwen waarmee u de volgorde kunt volgen (wanneer u bijvoorbeeld kolommen ORDER BY uit verschillende tabellen), dus de vuistregel in dergelijke situaties is om de gegevens die MySQL zal plaatsen te minimaliseren in de tijdelijke tabel. Hoe kan je het doen? U selecteert alleen id's van de rijen in een subquery en nadat u de id's hebt, voegt u de id's samen met de tabel zelf en andere tabellen om de inhoud op te halen. Dat wil zeggen dat je een kleine tafel maakt met een bestelling en vervolgens het snelle scenario gebruikt. (Dit is enigszins in tegenspraak met SQL in het algemeen, maar elke vorm van SQL heeft zijn eigen middelen om query's op die manier te optimaliseren).
Toevallig, je SELECT -- everything is ok here
ziet er grappig uit, omdat het de eerste plaats is waar het niet goed is.
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)
;
Dat is de eerste stap, maar zelfs nu kun je zien dat je deze nutteloze LEFT JOINS en json-serialisaties niet hoeft te maken voor de rijen die je niet nodig hebt. (Ik heb GROUP BY p.id
overgeslagen , omdat ik niet zie welke LEFT JOIN kan resulteren in meerdere rijen, doe je geen aggregatie).
nog over te schrijven:
- indexen
- formuleer de CASE-clausule opnieuw (gebruik UNION ALL)
- waarschijnlijk een index forceren