24. august 2006 - 16:35Der er
5 kommentarer og 1 løsning
Overdrevet hastighedsreduktion ved brug af IN i et JOIN?
Hej eksperter,
Følgende sql-kald henter de 5 seneste topics i de fora, som $user_id har valgt som favorit-fora:
SELECT ft.title, ft.views, ft.replies, f.forum_id, f.title forum_title FROM forum_topics ft JOIN forums f ON f.forum_id IN (SELECT forum_id FROM forum_favorites WHERE user_id = '".$user_id."') JOIN forum_posts fp ON ft.last_post_id = fp.post_id WHERE ft.type = '0' ORDER BY ft.last_post_id LIMIT 5
Det kald bliver udført på 0,33 sekunder - for langsomt!
Jeg har undersøgt sub-queryen og den returnerer 3 forum_id'er. Hvis jeg erstatter ovenstående query med denne query:
SELECT ft.title, ft.views, ft.replies, f.forum_id, f.title forum_title FROM forum_topics ft JOIN forums f ON f.forum_id = 'X' JOIN forum_posts fp ON ft.last_post_id = fp.post_id WHERE ft.type = '0' ORDER BY ft.last_post_id LIMIT 5
hvor X er ét af de tre fora som $user_id har som favorit - så køres hver af disse på ~0,00 sekunder. Man skulle tro af summen af de tre kald skulle være lig eksvekveringstiden på det oprindelig kald med IN()... eller hvad?
Ved at bruge explain på det oprindelig kald med IN kan jeg se at der ingen indexes bruges til tabellerne forum og forum_topics - og det er selve den ORDER BY ft.last_post_id der er skyld i forespørgslen tager lang tid, fordi den af en eller anden grund ikke kan bruge indexet på den kolonne. Jeg har uden held forsøgt med FORCE INDEX(). Faktisk skriver EXPLAIN på den oprindelige forespørgsel NULL i possible_keys på både forums og forum_topics... selvom den naturligvis findes nogle keys/indexes.
Jeg kører en MySQL 4.1.15-Debian_1-log hvis det skulle have interesse.
... ups jeg manglede lige en AND ft.forum_id = f.forum_id på det ene JOIN - men efter at have testet med dette kan jeg stadig ikke finde forklaringen på hvorfor kaldet med IN() er så meget langsommere.
Hvad hvis du nu omskriver den til ikke at benytte en subquery. Måske kan den så bedre udnytte de indexes der er oprettet... Jeg tror den skal se nogenlunde sådan her ud:
SELECT ft.title, ft.views, ft.replies, f.forum_id, f.title forum_title FROM forum_topics ft CROSS JOIN forum_favorites ff INNER JOIN forums f ON f.forum_id = ff.forum_id INNER JOIN forum_posts fp ON ft.last_post_id = fp.post_id WHERE ft.type = '0' AND ff.user_id = '".$user_id."' ORDER BY ft.last_post_id LIMIT 5
Lige nu undrer jeg mig bare over hvorfor jeg egentlig ville have den subquery med i første omgang. Men så er det jo godt I kan hjælpe mig :-)
Umiddelbart bryder jeg mig ikke om cross-join'ens syntax. Jeg synes det er mere logisk at have hvert eneste joins "join-clause" i den efterfølgende ON... ellers synes jeg WHERE'en nemt kan blive meget rodet :-) Så jeg har tilladet mig at omskrive dit kald (hvis nogen sku ha interesse) til dette:
SELECT ft.title, ft.views, ft.replies, f.forum_id, f.title forum_title FROM forum_topics ft JOIN forum_favorites ff ON ff.user_id = '278' JOIN forums f ON f.forum_id = ff.forum_id JOIN forum_posts fp ON ft.last_post_id = fp.post_id WHERE ft.type = '0' ORDER BY ft.last_post_id LIMIT 5
Smag og behag er heldigvis forskellig. :-) Bare det er effektivt, betyder formen jo mindre...
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.