Detta är en mycket intressant fråga. Under optimeringen kan du upptäcka och förstå mycket ny information om hur MySQL fungerar. Jag är inte säker på att jag kommer att hinna skriva allt i detaljer på en gång, men jag kan gradvis uppdatera.
Varför det går långsamt
Det finns i princip två scenarier:ett snabbt och en långsam .
I en snabb scenario du går i någon fördefinierad ordning över en tabell och förmodligen samtidigt snabbt hämta lite data efter id för varje rad från andra tabeller. I det här fallet slutar du gå så snart du har tillräckligt många rader specificerade av din LIMIT-sats. Var kommer beställningen ifrån? Från ett b-trädindex som du har på tabellen eller ordningen för en resultatuppsättning i en underfråga.
I en långsam scenario du inte har den fördefinierade ordningen, och MySQL måste implicit lägga all data i en temporär tabell, sortera tabellen på något fält och returnera n rader från din LIMIT-sats. Om något av fälten som du lägger in i den temporära tabellen är av typen TEXT (inte VARCHAR), försöker MySQL inte ens behålla den tabellen i RAM-minnet och rensar och sorterar den på disken (därav ytterligare IO-bearbetning).
Det första att fixa
Det finns många situationer när du inte kan bygga ett index som gör att du kan följa dess ordning (när du t.ex. BESTÄLLER EFTER kolumner från olika tabeller), så tumregeln i sådana situationer är att minimera data som MySQL kommer att lägga i den tillfälliga tabellen. Hur kan du göra det? Du väljer bara identifierare för raderna i en underfråga och efter att du har id:n kopplar du ihop id:n till själva tabellen och andra tabeller för att hämta innehållet. Det vill säga du gör ett litet bord med en beställning och använder sedan snabbscenariot. (Detta strider lite mot SQL i allmänhet, men varje variant av SQL har sina egna sätt att optimera frågor på det sättet).
Av en slump är din SELECT -- everything is ok here
ser roligt ut, eftersom det är det första stället där det inte är ok.
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)
;
Det är det första steget, men redan nu kan du se att du inte behöver göra dessa värdelösa LEFT JOINS och json-serialiseringar för de rader du inte behöver. (Jag hoppade över GROUP BY p.id
, eftersom jag inte ser vilken LEFT JOIN som kan resultera i flera rader, gör du ingen aggregering).
ännu att skriva om:
- index
- formulera om CASE-satsen (använd UNION ALL)
- förmodligen tvingar fram ett index