![]() |
Prestandaproblem med komplicerad SQL och 400.000 rader
Nu fick jag en svårare nöt att knäcka. Jag har en tabell som innehåller ca 400.000 rader och alla (nästan) har en latitud/longitud koordinat. Jag vill då skriva frågor som innebär att lista de närmaste raderna inom en viss radie utifrån en given punkt. Exempel:
SELECT *, ( 3959 * ACOS( COS( RADIANS(AAA) ) * COS( RADIANS( LAT ) ) * COS( RADIANS( LONG ) - RADIANS(OOO) ) + SIN( RADIANS(AAA) ) * SIN( RADIANS( LAT ) ) ) ) AS distance FROM tabellen_ifråga HAVING distance < X ORDER BY distance Där är AAA latituden, OOO är longituden och X är avståndet (radien) utifrån utgångspunkten. Problemet är inte frågan i sig (utan den får de svar den ska ha), utan mitt problem är prestandan. Att köra ovannämnda fråga kan ta ca 1 sekund och i vissa fall kan jag behöva att köra den 3 gånger på en sidvisning. Tabeller ser ut enl. nedan: ID - PK LONG - VARCHAR(15) - INDEX LAT - VARCHAR(15) - INDEX Exempel på en koordinat kan vara: LONG: -84.093177 LAT: 35.922934 Jag kör MySQL 5.1.58 och MyISAM som db-motor (standard) Vad kan jag göra? Jag kör detta på en vanlig VPS hos Glesys med Debian 6, 64-bit Tips mottages gladligen! |
Jag brukar köra en betydligt enklare avståndsformel i databasqueryn och först därefter räkna mer exakt avstånd på de utvalda. Trigonometriska funktioner är tunga.
|
Citat:
|
Pröva gärna postgresql och ta fram avstånd direkt med ST_Distance i postgis.
|
Citat:
|
Vad har du för prestanda på den maskinen? Har du provat att lägga på mer power och se om det blir något större förändring?
|
Varför köra den flera gånger, är det med olika X?
|
På en VPS hos Glesys kan man välja antal kärnor man vill ha, men inte hur "snabba" dom ska vara. Av eller på, typ.
Citat:
|
Själva grundproblemet här är att din query inte kan använda några index -- och blir därför väldigt slö eftersom den måste söka igenom hela tabellen och applicera din formel på alla rader. En snabbare CPU/biffigare server är knappast lösningen på problemet. 400 000 rader som är indexerade kräver inte någon extrem hårdvara att söka igenom snabbt.
En lösning skulle kunna vara typ: SELECT *, <lång formel> AS distance FROM tabellen_ifråga WHERE (lat BETWEEN A AND B) AND (lon BETWEEN C AND D) HAVING distance < X ORDER BY distance Där A, B, C och D är yttre gränser för platser som inte är intressanta. Om gränserna uppskattas vettigt så borde de skära bort den största delen av alla rader effektivt, och sen kan resten sökas igenom med den "tunga" formeln. Det är viktigt att lat/lon är indexerade så att de kan sökas effektivt (som du har skrivit att de redan är). Är inte helt säker på hur bra jämförelserna blir när du lagrar LAT/LONG som VARCHAR (minustecknet kan ställa till problem), det vore nog lämpligare (och garanterat effektivare) med en numerisk datatyp. MySQL känns i det här fallet inte som den bästa lösningen på problemet. Som Magnus_A skriver så skulle PostgreSQL/PostGIS lösa problemet väldigt enkelt med ett spatial index och ST_Distance. MySQL har även en liknande spatial extension, som inte är speciell komplett, och är inte säker på att den kan lösa problemet effektivt överhuvudtaget (men jag kan ha fel!). MongoDB har även stöd för spatial indexes och distans-queries, om du kunde lägga dina koordinater där och göra själva distans-sökningen där skulle det kunna vara ett alternativ. Jag är rädd att det inte finns någon riktigt bra lösning med MySQL förutom någon slags workaround som ovan, som både är krånligare, mindre effektiv och framför allt: fulare! ;) |
Känns ändå som att detta gav mig lite ledtrådar.
Istället för att använda en jobbig formel (som troligen är ganska krävande) så skulle man kanske kunna använda sig av en kvadrat istället för radie. Exempelvis att man ställer frågan utifrån LAT plus/minus X och LONG plus/minus X (med hjälp av t.ex. BETWEEN). Att jag får lite mer svar än jag skulle fått med den jobbiga formeln gör inte så mycket för det kan jag enkelt sortera bort med en liknande php-formel. Kanske kunde vara något för mig att testa. Kan nog vara värt att testa att konvertera columnen till lämplig datatyp med innan man gör något annat först. Citat:
|
andreasp har svarat samma sak kom jag på
|
Testa att byta datatyp till FLOAT.
Dina index blir väl som sagt inte så effektiva eftersom queryn ändå måste söka igenom allt för att göra beräkningar. Tyvärr är det ju även svårt att cahcea den typen av query då det är sällan man får exakt samma koordinater som indata vilket gör att det blir få cahce-träffar. Har du en valtrafikerad site kanske du kan lösa det på något sätt med någon sökmotorsmjukvara som t.ex. Solr eller ElasticSearch. Det kommer dock innebära en hel del konfigurerade. Edit igen: Det verkar finnas en del om man söker lite: http://www.google.se/search?q=Elasti...h+geo+distance http://www.elasticsearch.org/guide/r...nce-facet.html |
Citat:
Sqlite har också en spatial extension men det är knappast praktiskt möjligt med de antal rader du har. |
Tack alla för feedback!
Jag har kommit fram till att trimma min befintliga lösning. Ändrade datatypen till float och gjorde en avgränsning av koordinaterna i sql-frågan (med hjälp av BETWEEN och +- en viss marginal på utgångskoordinaten) för att sen finjustera med php. Resultatet blev att laddningstiden sjönk nästan med hälften. En laddningstid på drygt en sekund på de tunga sidorna känns som att jag får acceptera. De flesta sidorna har en laddningstid på 0,1 sekunder numera (mot ca 0,4 innan). Laddningstid = tid för att generera färdig html. |
Citat:
Vore intressant om ville dela med dig av följande: * hur skapar du den rektangeln som du selekterar SQL-en med - utifrån från centrumpunkten och utvidgar den till en fyrkant med (cirkelradien * 2) som sidlängd? * hur arbetar du med finjusteringen i PHP Jag har gjort liknande saker, och en något som snabbade upp för mig var att bryta ut long + lat + "POI-id" till en egen tabell och lägga på index på long och lat. Men det beror ju lite på databasdesign & mängd data om det lönar sig. Vissa värden i sql-cirkelformeln går även att förkalkylera utanför databaslagret. Mvh Robert |
Citat:
Jag skulle också tycka att det skulle vara väldigt intressant att höra lite mer om hur du gjort eller till och med få se lite kod. Gör du beräkningen och avståndet och sorteringen i (My)SQL eller PHP? Det där fungerar nog bra så länge du inte har allt för många besökare, men det är ju en sån sak som kan sänka servern vid mycket trafik. |
Absolut!
I grova drag handlar problemet om att jag vill ha ut närliggande koordinater utifrån en fast punkt. Jag började med att räkna ut hur stor en kvadrat blir som precis omger cirkeln med samma radie som jag är ute efter (ex 20 miles). En av sidorna på kvadraten är därmed 40 miles. Det längsta avståndet inom kvadraten mätt ifrån mittpunkten är till ett av hörnen och blir i detta fallet ca 28,3 miles (pythagoras sats). Andra delen i lösningen är att hitta rätt felmarginal i koordinaterna som motsvarar max 28,3 miles (jag landade på ca 29 miles enl. nedanstående exempel). Exempel (koordinaten är tagen ur luften): $lat: 30.1234 $long: -63.5552 $p: 0.35 (kom jag fram till via trial & error) ($p är "felmarginalskoordinat") Så jag körde en SELECT med dom data jag ville ha ut och körde med villkoren $sql = "SELECT LAT,LONG FROM tabellen WHERE (LAT BETWEEN ".($lat-$p)." AND ".($lat+$p).") AND (LONG BETWEEN ".($long-$p)." AND ".($long+$p).")"; Därefter kör jag en avståndsberäkning mellan svaren och min fasta punkt och sorterar med PHP enl. tex: http://www.web-max.ca/PHP/misc_2.php Efter sorteringen kan jag enkelt sortera bort dom som ligger utanför cirkelradien. Ungefär så... Citat:
|
Jag la upp en test i SQL-server 2008 R2 för att jämföra prestanda (och lära mig någt nytt om Global positionering). Jag upplever dock inget problem med prestandan. De skillnader jag har är att LONG och LAT är float som datatyper, satte upp två sammansatta index (IDX_LONGLAT(LONG,LAT) och IDX_LATLONG(LAT,LONG)) och att jag la upp SQL-frågorna som Stored Procedures, det går blixtsnabbt när jag kör frågan.
Först script för att skapa tabellen: Kod:
USE [wntest] Kod:
/****** Object: StoredProcedure [dbo].[CreateRandomPositions] Script Date: 12/02/2011 11:38:23 ******/ Stored procedure för att få fram det du ville ha: Kod:
/****** Object: StoredProcedure [dbo].[GetDist] Script Date: 12/02/2011 11:38:23 ******/ Jag var tvungen att först skapa testdata med min sp för detta: Kod:
EXEC CreateRamdomPositions; Kod:
EXEC GetDist -84.093177, 35.922934, 50; Kod:
GEOGRAPHIC_ID LONG LAT distance En klar rekommendation är att du kör så mycket som möjligt av urvalet i SQL så att systemet behöver skyffla så lite data som möjligt. SQL-motorn är mycket effektiv och har en hel del automatiska optimeringar som man slipper tänka på som utvecklare. Jag har tidigare kört MySQL med den riktiga SQL-motorn (dvs ej ISAM som är en extremt gammal teknik) och nu går det ju från version 5 att köra SP i MySQL. När jag testar performancetiderna med följande test: Kod:
declare @stop Datetime Efter att ha lagt till det extra urvalsvillkoret med en kvadrat innan den noggranna avståndskalkylen så ÖKADE körningstider till tider som överstiger 2200 millisekunder. Med 50 miles avståndskrav. Det tog m a o längre tid. Jag vet inte vad det beror på ännu. |
Okej, tack!
Om man vill variera radien borde konstanten vara någonstans runt 0,009 per km om man kollar här: http://en.wikipedia.org/wiki/Latitude#Degree_length 1/112(för att vara säker på att få med allt) ~ 0,008 per km Konstanten kan man ju anpassa en del och ha olika för latituden och longituden efter vart man är för longituden varierar ju väldigt mycket beroende på vart man är. |
Kan nämna att jag fick ner laddningstiden till i snitt 0.15 sekunder. Lösningen var att fixa ett nybörjarmisstag...
Det var inte databassökningarna som var problemet. Problemet var PHP. Jag tänkte inte på att avgränsa mina sql-frågor, så vissa frågor gav 30.000 rader som svar, och det tar en stund för PHP att bearbeta (att skicka från databasen till en variabel i php antar jag). I och med att jag aldrig vill ha mer än 20 svar i min lista, så körde jag en LIMIT 0,20 på sql-frågan och vips var laddningstiden ner till en tiondel på de tyngsta sidladdningarna. Ifall att någon ville veta ;-) |
Citat:
|
Citat:
Men såg att det fanns en speciell datatyp för detta, ska testa om jag kan pressa tiden med detta.... Kul tankenöt att jobba med dock... |
Citat:
Först gjorde jag som jag tror att du gjort och körde sedan alter table positions add index (lat, lon); vilket gjorde det lite lite snabare. |
Citat:
|
Alla tider är GMT +2. Klockan är nu 11:24. |
Programvara från: vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Svensk översättning av: Anders Pettersson