Query optimaliseren? gebruikt filesort en duurt vrij lang.
Door
ericbruggema x
op 02-04-2013 08:10
gewijzigd op 02-04-2013 09:06
2.494 views
Iemand een idee hoe ik deze query beter kan schrijven?
SELECT `plaatjes`.*
FROM `plaatjes`
LEFT JOIN `views` ON `views`.`category` = `plaatjes`.`category`
WHERE YEAR(`views`.`date`) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(`views`.`date`) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)
GROUP BY `views`.`category`, `plaatjes`.`category`
ORDER BY `views`.`date` DESC
LIMIT 0,5
structuur en indexes
CREATE TABLE IF NOT EXISTS `plaatjes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`category` varchar(32) CHARACTER SET latin1 NOT NULL,
`filetype` tinyint(3) unsigned NOT NULL,
`filename` varchar(40) CHARACTER SET latin1 NOT NULL,
`filesize` int(10) unsigned NOT NULL,
`height` int(10) unsigned NOT NULL,
`width` int(10) unsigned NOT NULL,
`animated` enum('j','n') CHARACTER SET latin1 NOT NULL DEFAULT 'n',
`views` int(10) unsigned NOT NULL,
`lastview` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `category` (`category`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC AUTO_INCREMENT=28350 ;
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `views` (
`category` varchar(32) NOT NULL,
`date` date NOT NULL,
`tstamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`views` int(10) unsigned NOT NULL,
`thumbs` int(10) unsigned NOT NULL,
`searches` int(10) unsigned NOT NULL,
UNIQUE KEY `category` (`category`,`date`),
KEY `date` (`date`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
maar deze gebruikt filesort, iets wat ik graag wil voorkomen aangezien dat nogal in de snelheid beperkt.
Ene site laad met 5.000 view records in 4 seconden andere site met 35k records in views laad in 12-20 seconden :{
1,SIMPLE,views,index,category,category,37,NULL,35317,Using where; Using index; Using temporary; Using f...
1,SIMPLE,plaatjes,ref,category,category,34,nvt****_anipl.views.category,17
Ah, zal aan de LIMIT liggen denk ik. Ik denk dat die LIMIT wordt geforceerd voor de GROUP BY wordt uitgevoerd. Probeer eens de LIMIT uit de subquery te halen en aan het einde te zetten:
SELECT plaatjes.*
FROM (
SELECT MAX(plaatjes.id) AS id, plaatjes.category, a.date_viewed
FROM (
SELECT category, MAX(date) AS date_viewed
FROM views
WHERE date BETWEEN date_sub(CURDATE(), INTERVAL '1' month) and CURDATE()
GROUP BY category
) a
LEFT JOIN plaatjes ON a.category = plaatjes.category
GROUP BY plaatjes.category, a.date_viewed
) b
LEFT JOIN plaatjes ON b.id = plaatjes.id
ORDER BY b.date_viewed DESC
LIMIT 5
Dit zou wel kunnen betekenen dat de query trager wordt. Als het weer te traag wordt kan het nog wel iets omgebouwd worden.
Ja qua tijd scheelt het zeker een stuk maar zit per query nog ruim boven de 1seconde ;{
Zit er nu te denken of het niet handiger is om deze query gewoon op te splitsen in 2, dan lees ik eerst de views uit en pak daarna de plaatjes er bij...
Maar na een test hier, zou het eerdere probleem van die NULL waardes niet in die LIMIT moeten zitten. Die zou gewoon de juiste waardes moeten opleveren en als er minder dan 5 rijen zijn er ook minder terug moeten geven.
Een andere mogelijkheid is nog om beide LEFT JOINs om te schrijven naar een INNER JOIN. Als dat het probleem ook niet verhelpt dan moet je dieper kijken naar waar precies het probleem zit. Ergens in een subquery krijg je dan gegevens terug die niet de bedoeling zijn, maar puur op de code (zonder enige inzicht in je data) kan ik dat verder niet doen.
Erwin, dat 2 queries langzamer zijn dan 1 hoeft niet altijd zo te zijn, heb zelf een statistieken script in elkaar gedraaid en moest daarvoor 1 query verdelen in 2 om de preformance goed te houden maar goed, dat was een heel ander project, vele jaren geleden.
Maar goed, de query is nog niet zo snel als dat ik verwacht had. Er moet toch een andere oplossing zijn, als je wilt kan ik je via PM wel inlog gegevens geven zodat je kunt spelen met de database of als je wilt heb ik hier wel een sql bestand met alle records.
Heb het nu tijdelijk maar even gecached, scheelt een berg maar is niet de oplossing die ik voor ogen had! :)
Interessant, wat bij mij voornamelijk de vraag oproept of die enkele query dan toch nog veel beter te optimaliseren valt. Op zich kan het niet zo zijn dat je met de overhead van een extra database aanroep toch sneller kunt zijn. Overigens ga ik er wel vanuit dat er ook daadwerkelijk uitkomsten uit komen in beide gevallen, anders is het verschil natuurlijk snel te verklaren....
Nu overigens geen puf om nog eens naar die query te kijken, misschien morgen.
Dit is ook een beetje appels met peren vergelijken. de twee queries in 1 query:
SELECT v.category, v.date, p.filename
FROM
(SELECT category, date
FROM views
WHERE date BETWEEN CURRENT_DATE - INTERVAL 1 MONTH AND CURRENT_DATE
ORDER BY date DESC LIMIT 5) v
JOIN
plaatjes p ON v.category = p.category
GROUP BY v.category
Wat dus een oneigenlijk gebruik van group by is (in Postgres kan dit niet eens).
Het best bekeken plaatje van een categorie erbij halen:
SELECT v.category, v.date, p.filename, p.views, MAX(p.views) max_views
FROM
(SELECT category, date
FROM views
WHERE date BETWEEN CURRENT_DATE - INTERVAL 1 MONTH AND CURRENT_DATE
ORDER BY date DESC LIMIT 5) v
JOIN
plaatjes p ON v.category = p.category
GROUP BY v.category, v.date, p.filename, p.views
HAVING p.views = MAX(p.views)
Allemaal bedankt voor jullie reacties, wat ik ook heb geprobeerd uit eindelijk is de versie met 2 queries vele malen sneller.... waarom? joost mag het weten, gebruik dan wel optimaal de 'simpele' indexen (simpele site zou je zeggen..).
Heb zeker wat geleerd dus het was zeker niet voor niets!
Bedankt!