Ongelooflijk trage queries met ZF

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Pagina: 1 2 volgende »

Chris -

Chris -

24/07/2012 14:08:23
Quote Anchor link
Goedemiddag heren,

Voor een website heb ik een aantal vrij grote queries moeten schrijven om de zoekmachine genoeg opties te geven.

Nu maak ik gebruik van drie inner joins om te zoeken op specifieke tabellen (properties tabellen).

In het begin was deze vrij snel omdat er maar 1 inner join was, maar omdat er twee extra velden bij moesten komen is dit nu ongelooflijk traag geworden! Het zoeken duurt nu zo'n 10-15 seconden.

Dit heb ik tijdelijk opgelost door het cachen van de resultaat, maar als de cache moet worden vernieuwd duurt het weer een eeuwigheid.

Ik heb al wat indexes aangemaakt voornamelijk waar de joins op komen, maar heeft niet veel uitgemaakt. Waar kan het aan liggen?

Groeten,
Chris
 
PHP hulp

PHP hulp

15/05/2024 01:25:39
 
John Cena

John Cena

24/07/2012 14:27:09
Quote Anchor link
Zonder tabel opbouw & data is het moeilijk raden :)

Daarnaast, cache tabellen opbouwen kan ook met een cronjob
 
TJVB tvb

TJVB tvb

24/07/2012 14:54:19
Quote Anchor link
Geef eens je database structuur (inclusief keys etc), je query en de explain van je query.
Dan kunnen we meekijken
 
Erwin H

Erwin H

24/07/2012 15:47:50
Quote Anchor link
Zonder inzicht in de database is er natuurlijk ook wel iets te zeggen. Ten eerste, als je het nog niet kent, check met EXPLAIN eens hoe je query wordt opgebouwd, welke indices worden gebruikt en waar je mogelijk performance kan verbeteren. Een duidelijke tutorial en uitleg over explain:
http://www.slideshare.net/phpcodemonkey/mysql-explain-explained

Verder, aangezien het een search engine betreft, heb je erg veel LIKE statements in je query staan? Die zijn over het algemeen erg traag en als het even kan zou ik daar de aandacht op vestigen. Wellicht dat je die anders kan opstellen, of misschien zelfs wel je hele query opbouw anders. Het scheelt namelijk nogal of je zo'n LIKE doet op de hele tabel of alleen op een subset. Kan je dus je tabel verkleinen (binnen de query) dan kan dat snel al veel tijdswinst opleveren.
 
Chris -

Chris -

24/07/2012 16:50:12
Quote Anchor link
Heren, kan niet zomaar de volledige database neerleggen, zit onder contract :-)
Om jullie niet geheel blind te laten staren, heb ik een voorbeeld neergezet om in het algemeen te laten zien hoe het in elkaar zit.

tabel1: - id (primary/index), is_online
tabel1_props: - id(primary), tabel1_id (index), property_name (index), property_value

In princiepe zijn er geen likes in de query. Het is de bedoeling om meerdere properties op te halen die zijn gekoppeld aan tabel1. Maar, omdat er 3 properties in totaal moeten worden opgehaald, die los van elkaar staan, maar met dezelfde tabel1_id, heb ik drie keer in INNER JOIN gedaan. Had ergens gelezen dat een INNER sneller was dan een LEFT( als dat uberhaupt al klopt?)

De query is de volgende (een kleinere query, omdat er nu toevallig wordt gezocht op slechts 1 property. Deze query however, neemt meer dan 30 seconden in beslag(?!):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT tabel1.id
FROM tabel1
INNER JOIN
    tabel1_props
    ON
    tabel1.id = tabel1_props.tabel1_id
INNER JOIN
    tabel1_props AS tabel1
    ON
    tabel1.id = tabel1_props2.tabel1_id
INNER JOIN
    tabel1_props AS tabel1_props3
    ON
    tabel1.id = tabel1_props3.tabel1_id    
WHERE tabel1.is_online = 1
AND (
    tabel1_props.prop_name = 'specifieke_property'
    AND
    tabel1_props.prop_value
    BETWEEN :pricestart
    AND :priceend
)

Zoals je ziet prepared statements.

De fout is tijdens het schrijven van de reactie al opgelost. Ik wilde een explain doen, deed een verkeerde copy/paste en hij draaide de volledige query. 14.000 records met tabel1.id kwam omhoog. Oftewel, de oplossing is vrij simpel; GROUP BY :)

Doordat de query zoals deze werd opgebouwd in de code zelf bestond uit een kleine 250 regels, hier volledig overheen gekeken.. thanks jongens!
 
TJVB tvb

TJVB tvb

24/07/2012 17:01:23
Quote Anchor link
Ik denk (zou het moeten testen om het zeker te weten)
Dat als je op tabel1_props.prop_name een index zet en je between omzet in 2 vergelijkingen de query nog sneller is. Dat kun je met een explain ook bekijken
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SELECT tabel1.id
FROM tabel1
INNER JOIN
    tabel1_props
    ON
    tabel1.id = tabel1_props.tabel1_id
INNER JOIN
    tabel1_props AS tabel1
    ON
    tabel1.id = tabel1_props2.tabel1_id
INNER JOIN
    tabel1_props AS tabel1_props3
    ON
    tabel1.id = tabel1_props3.tabel1_id    
WHERE tabel1.is_online = 1
AND (
    tabel1_props.prop_name = 'specifieke_property' AND
    tabel1_props.prop_value >= :pricestart AND
    tabel1_props.prop_value <= :priceend
)
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

24/07/2012 17:35:34
Quote Anchor link
Chris op 24/07/2012 16:50:12:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT tabel1.id
FROM tabel1
INNER JOIN
    tabel1_props
    ON
    tabel1.id = tabel1_props.tabel1_id
INNER JOIN
    tabel1_props AS tabel1
    ON
    tabel1.id = tabel1_props2.tabel1_id
INNER JOIN
    tabel1_props AS tabel1_props3
    ON
    tabel1.id = tabel1_props3.tabel1_id    
WHERE tabel1.is_online = 1
AND (
    tabel1_props.prop_name = 'specifieke_property'
    AND
    tabel1_props.prop_value
    BETWEEN :pricestart
    AND :priceend
)

Zoals je ziet prepared statements.

De fout is tijdens het schrijven van de reactie al opgelost. Ik wilde een explain doen, deed een verkeerde copy/paste en hij draaide de volledige query. 14.000 records met tabel1.id kwam omhoog. Oftewel, de oplossing is vrij simpel; GROUP BY :)

Doordat de query zoals deze werd opgebouwd in de code zelf bestond uit een kleine 250 regels, hier volledig overheen gekeken.. thanks jongens!

GROUP BY is geen oplossing, je query is zo traag omdat je 3x hetzelfde doet, nl telkens joinen van dezelfde tabellen op dezelfde kolommen.

Offtopic:

Een prepared statement op een 1 select query levert geen enkele snelheids winst op, sterker nog het kost je snelheid
 
Chris -

Chris -

24/07/2012 17:42:38
Quote Anchor link
Prepared statements is voornamelijk voor een extra stukje veiligheid. De variabelen worden uitvoerig gecontroleerd, maar toch.

In deze query wordt er slechts 1 keer gezocht, maar het kan meerdere keren voorkomen. Doe ik die extra in de join, voert hij de query blijkbaar niet uit.. De GROUP by is inderdaad iets trager, maar een stuk sneller in de rest van de programmering. Lastig om uit te leggen, maar voor ieder ID voert hij een stukje uit. Die wordt overigens ook gecached, maar doordat hij het eerst 14.000 keer ipv 30 keer uitvoerde werd dat iets te traag..

Hoe denk jij dat het sneller kan? In de toekomst kan het wel voorkomen dat er 14.000 resultaten zijn. Hoe kan ik dat dan oplossen?
 
Eddy E

Eddy E

24/07/2012 17:51:26
Quote Anchor link
Je gaat dus alle resultaten af... en voert dan zo'n complexe query uit?
Dat moet niet hoeven. 1 zoekactie moet in 1 query kunnen.
 
Chris -

Chris -

24/07/2012 17:59:33
Quote Anchor link
De zoekactie zelf, haalt alleen de ID's op. Vervolgens wordt er voor die ID's een functie aangeroepen die alle overige informatie van dat ID ophaalt. Dit zijn in totaal een aantal queries en array's die samen gevoegd worden.

Het ophalen van de gegevens van zo'n ID wordt gecached omdat die op meerdere plekken wordt opgehaald.

Of heb jij een idee hoe je door middel van 1 query, een koppeling kan maken met 4 andere tabellen, en al die gegevens als array terug kan krijgen? Gaat hier om zo'n 30 properties per huis, en nog eens 2-20 properties. Die moete allemaal als array terugkomen. Is dat met 1 query uberhaupt te doen??

Toevoeging op 24/07/2012 18:08:36:

Voor de volledigheid, de query om alle ID's op te halen op basis van 3 properties:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
SELECT tabel1.id, tabel1.always_open
FROM tabel1
LEFT JOIN
    tabel2
    ON
        tabel1.id = tabel2.tabel1_id    
INNER JOIN
    tabel1_properties
    ON
        tabel1.id = tabel1_properties.tabel1_id    
INNER JOIN
    tabel1_properties AS tabel1_properties2
    ON
        tabel1.id = tabel1_properties2.tabel1_id
INNER JOIN
    tabel1_properties AS tabel1_properties3
    ON
        tabel1.id = tabel1_properties3.tabel1_id
WHERE (
    (
        tabel1.is_online = 1
        AND
        tabel2.calendar_date = :calendardate
    )
    OR
    (
        tabel1.is_online = 1
        AND
        tabel1.always_open = 1
    )
)                        
AND (
    tabel1_properties.prop_name = 'kenmerk1'
    AND
    tabel1_properties.prop_value
        BETWEEN :kenmerk1_1
        AND :kenmerk1_2
)
AND (
    tabel1_properties2.prop_name = 'kenmerk2'
    AND
    tabel1_properties2.prop_value != :kenmerk2
)
AND (
    tabel1_properties3.prop_name = 'kenmerk3'
    AND
    tabel1_properties3.prop_value != :kenmerk3
)
GROUP BY tabel1.id  
 
Eddy E

Eddy E

24/07/2012 18:19:44
Quote Anchor link
Waarom 3x dezelfde tabel openen (tabel1_properties)? Een keer zou toch genoeg moeten zijn? Of zijn er meerdere links tussen tabel tabel1 en tabel1???
Als ik dit zo zie denk ik: euh... ga eens normaliseren.

Een kolom met 'kenmerk3' is al fout.
Dat had gewoon een extra tabel moeten zijn met daarin een kolom 'eigenschap_id' en 'waarde_id'.
En dan nog een tabel voor de eigenschappen en eentje voor de waarden (als die categoriseerbaar zijn, zo niet: gewoon een VARCHAR of INT (afhankelijk van de waardes.



Toevoeging op 24/07/2012 18:20:18:

Kletter anders even je database (als dat kan) en anders alleen je structuur in http://sqlfiddle.com/
 
Chris -

Chris -

24/07/2012 18:21:24
Quote Anchor link
Eddy,

tabel1: id, is_online, always_online
tabel1_properties: id, tabel1_id, prop_name, prop_value
tabel2: id, tabel1_id, calendar_date

oftewel, het is al genormaliseerd. de prop_name is dus niet vast!

Toevoeging op 24/07/2012 18:22:37:

Voor de duidelijkheid, doe ik niet drie keer een INNER JOIN op tabel1_properties, dan kan hij de resultaten niet vinden! Doe ik wel drie keer INNER JOIN, vind hij pas de resultaten.
Gewijzigd op 24/07/2012 18:24:13 door Chris -
 
Erwin H

Erwin H

24/07/2012 18:37:01
Quote Anchor link
Even een klein stukje uit je bovenstaande query:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INNER JOIN
    tabel1_properties AS tabel1_properties2
    ON
        tabel1.id = tabel1_properties2.tabel1_id
INNER JOIN
    tabel1_properties AS tabel1_properties3
    ON
        tabel1.id = tabel1_properties3.tabel1_id

-- knip --

AND (
    tabel1_properties2.prop_name = 'kenmerk2'
    AND
    tabel1_properties2.prop_value != :kenmerk2
)
AND (
    tabel1_properties3.prop_name = 'kenmerk3'
    AND
    tabel1_properties3.prop_value != :kenmerk3
)

tabel1_properties2 en tabel1_properties3 is dezelfde tabel. Beide keren join je tabel1_properties maar geeft ze alleen een andere alias. Je joined ze ook op dezelfde kolommen, dus eigenlijk zou het zo moeten zijn dat tabel1_properties2.prop_name gelijk is aan tabel1_properties3.prop_name. Voor elke rij in je query.
Bovenstaande query zou dus alleen resultaten moeten opleveren als 'kenmerk2' = 'kenmerk3'.

Als dat niet zo is dan heb je denk ik ergens anders een fout zitten. Ik kan namelijk geen enkele situatie voorstellen waar niet zou gelden wat ik net heb geschreven.
 
Chris -

Chris -

24/07/2012 18:46:05
Quote Anchor link
Erwin, kun je mij een voorbeeld sturen hoe jij het aan zou pakken? vanuit tabel1_properties, moet ik 3 verschillende prop_name's selecteren met drie verschillende prop_value's, en verder ook de andere WHERE..

Ik had juist alles genormaliseerd omdat het makkelijker zou moeten zijn, maar in de tussentijd lijkt het alleen maar moeilijker en moeilijker te worden :S
 
Erwin H

Erwin H

24/07/2012 18:47:42
Quote Anchor link
Ik ben aan het zoeken, want ik heb een soortgelijk iets een tijdje terug ook gehad.
Duurt wel even, want het is inderdaad niet het makkelijkste :-)
 
Chris -

Chris -

24/07/2012 18:48:37
Quote Anchor link
Onwijs bedankt!
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

24/07/2012 18:55:01
Quote Anchor link
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
SELECT tabel1.id, tabel1.always_open [enderest]
FROM tabel1
LEFT JOIN
    tabel2
    ON
        tabel1.id = tabel2.tabel1_id    
LEFT JOIN
    tabel1_properties AS tp
    ON
        tabel1.id = tabel1_properties.tabel1_id    
WHERE (
    (
        tabel1.is_online = 1
        AND
        tabel2.calendar_date = :calendardate
    )
    OR
    (
        tabel1.is_online = 1
        AND
        tabel1.always_open = 1
    )
)                        
AND (
    (
            tp.prop_name = 'kenmerk1'
        AND
            tp.prop_value
                BETWEEN :kenmerk1_1  AND :kenmerk1_2
    )
    OR (
            tp.prop_name = 'kenmerk2'
            AND
            tp.prop_value != :kenmerk2
    )
    OR (
            tp.prop_name = 'kenmerk3'
            AND
            tp.prop_value != :kenmerk3
    ))
ORDER BY tabel1.id

Edit ik kan een haakje vergeten hebben

Toevoeging op 24/07/2012 18:56:27:

PS:
Je kan LEFT en INNER joins niet door elkaar gebruiken zonder subqueries
Gewijzigd op 24/07/2012 18:59:30 door Ger van Steenderen
 
Chris -

Chris -

24/07/2012 19:00:45
Quote Anchor link
Ger, nu heb je overal een OR staan, maar het zijn allemaal AND stukjes. Als ik de INNER in een LEFT JOIN verander, krijg ik alsnog géén resultaten. Met drie INNER JOINs wel.
 
Erwin H

Erwin H

24/07/2012 19:01:58
Quote Anchor link
Het probleem met je joins is dat je dus je dataset 3x zo groot maakt, voor je het gaat verminderen. Stap 1 als je een query wilt versnellen is proberen zo snel mogelijk de dataset te verkleinen.
Als ik het (nu wel) goed begrijp heb je die drie joins nodig omdat je elke rij verschillende kenmerken heeft in de properties tabel en je dus alle rijen moet vinden die alle drie de kenmerken hebben. Elke rij die het eerste kenmerk al niet heeft kan je dus meteen weggooien. Als je die voorwaarde al opneemt in de join voorwaarde zou dat moeten gebeuren. Op deze manier dus:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
SELECT tabel1.id, tabel1.always_open
FROM tabel1
LEFT JOIN
    tabel2
    ON
        tabel1.id = tabel2.tabel1_id    
LEFT JOIN
    tabel1_properties
    ON (
        tabel1.id = tabel1_properties.tabel1_id    
        AND tabel1_properties.prop_name = 'kenmerk1'
        AND tabel1_properties.prop_value
            BETWEEN :kenmerk1_1
            AND :kenmerk1_2
    )
LEFT JOIN
    tabel1_properties AS tabel1_properties2
    ON (
        tabel1.id = tabel1_properties2.tabel1_id
    AND tabel1_properties2.prop_name = 'kenmerk2'
        AND tabel1_properties2.prop_value != :kenmerk2
    )    
INNER JOIN
    tabel1_properties AS tabel1_properties3
    ON (
        tabel1.id = tabel1_properties3.tabel1_id
    AND tabel1_properties3.prop_name = 'kenmerk3'
        AND tabel1_properties3.prop_value != :kenmerk3
    )    
WHERE (
    (
        tabel1.is_online = 1
        AND
        tabel2.calendar_date = :calendardate
    )
    OR
    (
        tabel1.is_online = 1
        AND
        tabel1.always_open = 1
    )
)

Hierbij gebruik je dus het kenmerk van een LEFT JOIN dat die alleen die rijen selecteert die aan beide kanten van de join een rij heeft die voldoet aan de woorwaarden. Na de eerste join verlies je dus al gelijk alle rijen die het kenmerk niet hebben. Je tweede join is dus al sneller, je derder join nog sneller.

Een andere optie zou zijn om met subqueries te werken, maar ik weet niet of dat nu echte zal gaan helpen in de snelheid. Ik heb het niet helemaal uitgeschreven voor je, alleen de eerste join, de rest mag je zelf erbij bedenken:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT tabel1.id, tabel1.always_open
FROM tabel1
LEFT JOIN
    tabel2
    ON
        tabel1.id = tabel2.tabel1_id    
LEFT JOIN (
    SELECT DISTINCT(tabel1_id)
    FROM tabel1_properties
    WHERE tabel1_properties.prop_name = 'kenmerk1'
    AND tabel1_properties.prop_value
        BETWEEN :kenmerk1_1
        AND :kenmerk1_2
) AS tabel1_properties
    ON tabel1.id = tabel1_properties.tabel1_id
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

24/07/2012 19:49:10
Quote Anchor link
@Chris
Ik heb het wat geoptimaliseerd:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SELECT
    tabel1.id, tabel1.always_open --[enderest]
FROM
    tabel1
LEFT JOIN
    tabel2 ON tabel1.id = tabel2.tabel1_id    
LEFT JOIN
    tabel1_properties AS tp ON tabel1.id = tabel1_properties.tabel1_id    
WHERE (
        (tabel1.is_online = 1 AND tabel2.calendar_date = :calendardate)
        OR
        ( tabel1.is_online = 1 AND tabel1.always_open = 1)
      )                        
AND (
           (tp.prop_name = 'kenmerk1' AND tp.prop_value BETWEEN :kenmerk1_1  AND :kenmerk1_2)
    OR
        (tp.prop_name = 'kenmerk2'  AND tp.prop_value != :kenmerk2)
    OR
        (tp.prop_name = 'kenmerk3'  AND tp.prop_value != :kenmerk3)
    )
ORDER BY tabel1.id
 
Chris -

Chris -

24/07/2012 19:52:22
Quote Anchor link
Erwin H op 24/07/2012 19:01:58:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
SELECT tabel1.id, tabel1.always_open
FROM tabel1
LEFT JOIN
    tabel2
    ON
        tabel1.id = tabel2.tabel1_id    
LEFT JOIN
    tabel1_properties
    ON (
        tabel1.id = tabel1_properties.tabel1_id    
        AND tabel1_properties.prop_name = 'kenmerk1'
        AND tabel1_properties.prop_value
            BETWEEN :kenmerk1_1
            AND :kenmerk1_2
    )
LEFT JOIN
    tabel1_properties AS tabel1_properties2
    ON (
        tabel1.id = tabel1_properties2.tabel1_id
    AND tabel1_properties2.prop_name = 'kenmerk2'
        AND tabel1_properties2.prop_value != :kenmerk2
    )    
INNER JOIN
    tabel1_properties AS tabel1_properties3
    ON (
        tabel1.id = tabel1_properties3.tabel1_id
    AND tabel1_properties3.prop_name = 'kenmerk3'
        AND tabel1_properties3.prop_value != :kenmerk3
    )    
WHERE (
    (
        tabel1.is_online = 1
        AND
        tabel2.calendar_date = :calendardate
    )
    OR
    (
        tabel1.is_online = 1
        AND
        tabel1.always_open = 1
    )
)


YES! Dit is volgens mij de query die ik zoek! Ik herken 'm wel (heb hier eerder mee gewerkt net als jij maar kon niets terug vinden) en dit moet 'm wel zijn. Gelijk even checken!

Edit:
Toon Records 0 - 14 ( 15 totaal, Query duurde 0.0061 sec) :-) Onwijs, heerlijk dit! Eindelijk een snelle query! Ongelooflijk bedankt voor al jullie hulp, Erwin, jij helemaal!
Gewijzigd op 24/07/2012 19:57:29 door Chris -
 

Pagina: 1 2 volgende »



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.