webForumDet fria alternativet

Optimering av SQL-sats

7 svar · 598 visningar · startad av lillebror

lillebrorMedlem sedan apr. 20041 687 inlägg
#1

Hej,

Någon som har förslag på hur den här SQL-satsen kan optimeras? Jag konstruerade SQL-koden för två år sedan för en "Mina kontakter"-sida som varje medlem har. I takt med ökat antal kontakter i listan så verkar SQL-satsen ta längre och längre tid att exekvera. Jag misstänker att det är i SQL-koden som felet ligger. Nedan så har jag fetmarkerat det som jag tror ger upphov till segheten.

$q = "SELECT 
DISTINCT (SELECT username FROM tblmember WHERE memberID = t1.friendID) AS friendusername, 
(SELECT memberID FROM tblmember WHERE memberID = t1.friendID) AS memberID, 
(SELECT DATE_FORMAT(last_visit, '%Y-%m-%d %H:%i') FROM tblmember WHERE memberID = t1.friendID) AS datetime,
(SELECT memberID FROM tblonline WHERE memberID = t1.friendID) AS online,
(SELECT activity FROM tblonline WHERE memberID = t1.friendID) AS activity 
FROM 
tblmyfriends AS t1,
tblmember AS t2,
tblonline AS t3 [FONT=Arial][B]WHERE t1.memberID = $ID AND t2.suspended = 0[/B][/FONT] ORDER BY $orderby $sort";

Info från phpMyAdmin när jag kör frågan: Visar rader 0 - 284 (285 totalt, Frågan tog 96.0991 sek)

Någon som har ett bra förslag på hur SQL-satsen kan optimeras?!

Erik JuhlinMedlem sedan maj 20007 625 inlägg
#2

Nej, högst troligen inte det du fetmarkerat som gjort frågan seg.
Ganska många WTFs i den sql:en.

Prova nåt sånt här:

select
	m.username as friendusername,
	m.memberID,
	date_format(m.last_visit, '%Y-%m-%d %H:%i') as datetime,
	o.memberId as online,
	o.activity
from
	tblmyfriends f
	inner join tblmember m
		on m.memberID = f.friendID
	left outer join tblonline m
		on o.memberID = f.friendID
where
	f.memberID = $ID and
	m.suspended = 0
order by
	$orderby $sort
lillebrorMedlem sedan apr. 20041 687 inlägg
#3

Erik Juhlin skrev:

Ganska många WTFs i den sql:en.

Skulle du möjligtvis kunna exemplifiera några av dina WTFs? Det skulle göra det lättare för mig att förstå exakt vad det är som inte är bra.

lillebrorMedlem sedan apr. 20041 687 inlägg
#4

Erik Juhlin skrev:

select
	m.username as friendusername,
	m.memberID,
	date_format(m.last_visit, '%Y-%m-%d %H:%i') as datetime,
	o.memberId as online,
	o.activity
from
	tblmyfriends f
	inner join tblmember m
		on m.memberID = f.friendID
	left outer join tblonline [FONT=Arial][B]m[/B][/FONT]
		on o.memberID = f.friendID
where
	f.memberID = $ID and
	m.suspended = 0
order by
	$orderby $sort

Du hade missat en sak. Det fetmarkerade m:et skulle vara ett o istället. Fick nu en exekvertid på 0.02 sek. En klar förbättring! :)

Erik JuhlinMedlem sedan maj 20007 625 inlägg
#5

Med tanke på att koden är helt otestad så känns det ganska bra att det lilla felet var det enda. :)

Ska försöka hinna med senare att förklara lite vad som var dåligt.

lillebrorMedlem sedan apr. 20041 687 inlägg
#6

Erik Juhlin skrev:

Med tanke på att koden är helt otestad så känns det ganska bra att det lilla felet var det enda. :)

(y)

Erik Juhlin skrev:

Ska försöka hinna med senare att förklara lite vad som var dåligt.

Gör gärna det!

aasahMedlem sedan mars 20033 451 inlägg
#7

Vad WTF står för vet jag inte, men... din ursprungliga fråga är inte helt optimal.

FROM 
tblmyfriends AS t1,
tblmember AS t2,
tblonline AS t3 
WHERE t1.memberID = $ID AND t2.suspended = 0

Det här skapar en kryssprodukt mellan t1, t2 och t3. I princip kommer varje rad i t1 att kombineras med alla rader i t2, och var och en av resultatets rader kombineras med samtliga rader i t3. Om du har 10 rader i var och en av tabellerna, innehåller kryssprodukten från början 1000 rader... (och gissningsvis är dina tabeller större än 10 rader...)

När du har gjort det ser villkoret i where till att du "bara" behåller de rader där
t1.memberID har ett visst värde (vilket i mitt räkneexempel gäller 100 rader, om memberID är unikt i t1, och x*100 om det står x ggr). På samma sätt tar t2.suspended = 0 bort alla rader där detta inte är uppfyllt vilket väl troligen inte tar bort så värst många rader.

Men obs! att villkoret t1.memberID = $ID är uppfyllt för bra många rader där t1.friendID != memberID i antingen t2, t3 eller båda.

Du har alltså skapat en JÄTTEtabell, där andelen intressanta rader är en pytteliten minoritet.

För att (ur optimeringssynpunkt) göra saken värre använder du inte ens denna gigantiska tabell för att göra jämförelserna i direkt. Istället gör du för varje rad i din JÄTTEtabell en sökning av alla rader i en av de ursprungliga tabellerna efter en värdematchning, och detta görs en gång per kolumn du vill ta ut.

"SELECT DISTINCT 
(SELECT username FROM tblmember -- dvs samma som t2
WHERE memberID = t1.friendID) AS friendusername, 
(SELECT memberID FROM tblmember -- dvs samma som t2
WHERE memberID = t1.friendID) AS memberID, 
(SELECT DATE_FORMAT(last_visit, '%Y-%m-%d %H:%i') FROM tblmember -- dvs samma som t2 
WHERE memberID = t1.friendID) AS datetime,
(SELECT memberID FROM tblonline -- dvs samma som t3
WHERE memberID = t1.friendID) AS online,
(SELECT activity FROM tblonline -- dvs samma som t3
WHERE memberID = t1.friendID) AS activity

Så för varje rad i din jättetabell söker vi igenom t2 3 gånger, och t3 2 gånger. Låt oss säga att vi kom ner i att din jättetabell blev 90 rader stor. Då letar vi alltså igenom 90*(3*10)+ 90*(2*10) = 270+180=450 rader istället för 10.... som var det ursprungliga antalet rader i t1, t2 och t3. :o

(Nu har du visserligen troligen en optimerare någonstans som förbättrar frågan genom att formulera om den lite här och var innan den skickas för evaluering... men det finns gränser för vad den kan räta ut.)

Om vi jämför detta med Eriks kod....

  1. Vi joinar kontrollerat tabellerna så att det enda som slås ihop är de rader vi faktiskt var intresserade av:
from
	tblmyfriends f
	inner join tblmember m
		on m.memberID = f.friendID
	left outer join tblonline m
		on o.memberID = f.friendID

På vilken godtycklig rad som helst här kommer m.memberID att vara samma som o.memberID och f.friendID. WHERE-delen begränsar dessutom resultatet till att gälla de rader i alla tre tabellerna som hör ihop med bara ETT visst f.memberID och för icke-suspendade medlemmar.

select
	m.username as friendusername,
	m.memberID,
	date_format(m.last_visit, '%Y-%m-%d %H:%i') as datetime,
	o.memberId as online,
	o.activity

Här plockas dessutom kolumnvärdena ut från den resulterande sammanslagna tabellen, så vi behöver bara gå igenom den en gång.

Erik JuhlinMedlem sedan maj 20007 625 inlägg
#8

WTF fick jag från https://www.thedailywtf.com. Sen så kan det betyda både "What The F**k" eller "Worse Then Failure".
Sorry, har själv gjort en del tabbar som sql-nybörjare. :)

aasah förklarade allt fint så jag slipper. :)

Blev förresten lite fundersam på vart variablerna $ID $orderby $sort kommer ifrån. Risk för sql-injection?

131 ms totalt · 3 externa anrop · cache AV · v20260731051352-full.56cc9887
0 ms — hämta statistik (cache)
0 ms — hämta forumlista (cache)
128 ms — hämta tråd, inlägg och bilagor (db)