Toto je velmi zajímavý dotaz. Při jeho optimalizaci můžete objevit a pochopit spoustu nových informací o tom, jak MySQL funguje. Nejsem si jistý, že stihnu napsat vše do detailů najednou, ale mohu postupně aktualizovat.
Proč je to pomalé
V zásadě existují dva scénáře:rychlý a pomalé .
rychle Scénář procházíte v nějakém předdefinovaném pořadí nad tabulkou a pravděpodobně zároveň rychle získáváte některá data podle id pro každý řádek z jiných tabulek. V tomto případě přestanete chodit, jakmile budete mít dostatek řádků specifikovaných klauzulí LIMIT. Odkud pochází rozkaz? Z indexu b-stromu, který máte v tabulce, nebo pořadí sady výsledků v dílčím dotazu.
pomalu ve scénáři nemáte toto předdefinované pořadí a MySQL musí implicitně vložit všechna data do dočasné tabulky, seřadit tabulku podle nějakého pole a vrátit n řádků z vaší klauzule LIMIT. Pokud je některé z polí, která jste vložili do této dočasné tabulky, typu TEXT (nikoli VARCHAR), MySQL se ani nepokusí tuto tabulku ponechat v RAM a vyprázdní ji a seřadí ji na disk (proto další IO zpracování).
První věc, kterou je třeba opravit
Existuje mnoho situací, kdy nemůžete vytvořit index, který vám umožní sledovat jeho pořadí (například když ORDER BY sloupců z různých tabulek), takže pravidlem v takových situacích je minimalizovat data, která MySQL vloží v dočasné tabulce. jak to můžeš udělat? Vyberete pouze identifikátory řádků v dílčím dotazu a poté, co máte id, připojíte id k samotné tabulce a dalším tabulkám, abyste načetli obsah. To znamená, že vytvoříte malý stůl s objednávkou a poté použijete rychlý scénář. (To je trochu v rozporu s SQL obecně, ale každá varianta SQL má své vlastní prostředky k optimalizaci dotazů tímto způsobem).
Shodou okolností váš SELECT -- everything is ok here
vypadá legračně, protože je to první místo, kde to není v pořádku.
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)
;
To je první krok, ale i nyní můžete vidět, že tyto zbytečné LEFT JOINS a serializace json nemusíte dělat pro řádky, které nepotřebujete. (Přeskočil jsem GROUP BY p.id
, protože nevidím, který LEFT JOIN by mohl vést k několika řádkům, neprovádíte žádnou agregaci).
ještě o čem psát:
- indexy
- přeformulujte klauzuli CASE (použijte UNION ALL)
- pravděpodobně vynucení indexu