Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [mysql] optymalizacja kodu
Forum PHP.pl > Forum > Bazy danych > MySQL
crocodillo
Witam.

Mam taką funkcję:

  1. function n() {
  2. $n=sql::go(sprintf('SELECT * FROM
  3. (SELECT id as i1, latitude as x1, longitude as y1 FROM %1$s) as t1,
  4. (SELECT id as i2, latitude as x2, longitude as y2 FROM %1$s) as t2
  5. WHERE i1<i2 AND
  6. i1 NOT IN (SELECT id1 from %2$s WHERE id1 = i1 AND id2 = i2)
  7. LIMIT 3000',self::$tab_places,self::$tab_places_distance));
  8. echo '<pre>';
  9. if ($n) {
  10. while ($a=mysql_fetch_assoc($n)) {
  11. $ar=array(
  12. 'id1' => $a['i1'],
  13. 'id2' => $a['i2'],
  14. 'distance' => round(self::distanceFromCoords($a['x1'],$a['y1'],$a['x2'],$a['y2'])*1000)
  15. );
  16. print "$a[i1] - $a[i2] : $ar[distance]".PHP_EOL;
  17. sql::insertArr(self::$tab_places_distance,$ar);
  18. }
  19. }
  20. }


Nie będę tłumaczył wszystkiego, osoby znające się zrozumieją na pewno (po nazwach funkcji wiadomo o co chodzi).

Chodzi o to, że w jednej tabeli mam miejscowości wraz ze współrzędnymi geograficznymi, a w drugiej odległości między miejscowościami. W ten sposób jak ja to rozwiązałem to strasznie wolno chodzi (obecnie w bazie jest ok 1000 miejscowości). Co prawda obliczam tylko raz, ew. aktualizacja w przyszłości po dodaniu nowych miejscowości, ale tak z ciekawości chciałbym was zapytać, czy widzicie jakieś lepsze rozwiązanie?

Tak jak mam w przykładzie dla LIMIT 3000 u mnie na laptopie funkcja wykonuje się ponad 15 sekund. Kiedyś to zrobiłem bardziej w php, przeleciałem całą tabelę w pętlach, ale dzięki temu sposobowi łatwiej mi jest dodawać nowe miejscowości.
ActivePlayer
jak wyglada fu.kcja distance from coords. Nie moglbys jej na mysql przerzucic?
crocodillo
Właśnie jest tam trochę obliczeń:
  1. public function distanceFromCoords($lat1, $lan1, $lat2, $lan2) {
  2.  
  3. $lat1=deg2rad($lat1);
  4. $lat2=deg2rad($lat2);
  5. $lan1=deg2rad($lan1);
  6. $lan2=deg2rad($lan2);
  7.  
  8. return rad2deg(acos(sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($lan1-$lan2)))*111.18957696;
  9. }


Bałem się nawet myśleć o tym, żeby obliczenia robić w mysql smile.gif

Nawet nie chciałem próbować zrobić tego tylko w mysql, a tu proszę jak szybko poszło: całą bazę (niecałe 500tys) wygenerowane w niecałe 20 sekund:

  1. sql::go(sprintf(
  2. 'INSERT INTO %2$s (id1,id2,distance)
  3. (SELECT i1, i2, DEGREES(acos(sin(x1) * sin(x2) + cos(x1) * cos(x2) * cos(y1-y2)))*111189.57696
  4. FROM
  5. (SELECT id as i1, RADIANS(latitude) as x1, RADIANS(longitude) as y1 FROM %1$s) as t1,
  6. (SELECT id as i2, RADIANS(latitude) as x2, RADIANS(longitude) as y2 FROM %1$s) as t2
  7. WHERE i1<i2 AND
  8. i1 NOT IN (SELECT id1 from %2$s WHERE id1 = i1 AND id2 = i2)
  9. )',self::$tab_places,self::$tab_places_distance));


Ma ktoś może teraz sugestie, jak zoptymalizować to zapytanie?
ActivePlayer
Kod
explain SELECT i1, i2, DEGREES(acos(sin(x1) * sin(x2) +  cos(x1) * cos(x2) * cos(y1-y2)))*111189.57696
                FROM
                    (SELECT id as i1, RADIANS(latitude) as x1, RADIANS(longitude) as y1 FROM %1$s) as t1,
                    (SELECT id as i2, RADIANS(latitude) as x2, RADIANS(longitude) as y2 FROM %1$s) as t2
                    WHERE i1<i2 AND
                    i1 NOT IN (SELECT id1 from %2$s WHERE id1 = i1 AND id2 = i2

wykonaj i wklej wynik
crocodillo
Dzięki, już daję wynik:
  1. (
  2. [id] => 1
  3. [select_type] => PRIMARY
  4. [table] =>
  5. [type] => ALL
  6. [possible_keys] =>
  7. [key] =>
  8. [key_len] =>
  9. [ref] =>
  10. [rows] => 904
  11. [Extra] =>
  12. )
  13. (
  14. [id] => 1
  15. [select_type] => PRIMARY
  16. [table] =>
  17. [type] => ALL
  18. [possible_keys] =>
  19. [key] =>
  20. [key_len] =>
  21. [ref] =>
  22. [rows] => 904
  23. [Extra] => Using where; Using join buffer
  24. )
  25. (
  26. [id] => 4
  27. [select_type] => DEPENDENT SUBQUERY
  28. [table] => common_places_pl_dist
  29. [type] => ref
  30. [possible_keys] => id1
  31. [key] => id1
  32. [key_len] => 8
  33. [ref] => t1.i1,t2.i2
  34. [rows] => 1
  35. [Extra] => Using where; Using index
  36. )
  37. (
  38. [id] => 3
  39. [select_type] => DERIVED
  40. [table] => common_places_pl
  41. [type] => ALL
  42. [possible_keys] =>
  43. [key] =>
  44. [key_len] =>
  45. [ref] =>
  46. [rows] => 904
  47. [Extra] =>
  48. )
  49. (
  50. [id] => 2
  51. [select_type] => DERIVED
  52. [table] => common_places_pl
  53. [type] => ALL
  54. [possible_keys] =>
  55. [key] =>
  56. [key_len] =>
  57. [ref] =>
  58. [rows] => 904
  59. [Extra] =>
  60. )
ActivePlayer
bez danych nie potrafię Ci pomóc. nie wiem czy nie jesteś w stanie zamienić tych podzapytań na zwykłego joina, i druga sprawa jak robisz RADIANS(latitude) as x1 to za każdym razem mysql musi to policzyć, być może wyda się lepsze żeby te dane trzymać w dodatkowych kolumnach żeby nie trzeba było ich liczyć za każdym razem.
crocodillo
Dzięki. Odnośnie radianów, to wolę je liczyć, bo wykorzystuję je tylko i wyłącznie przy obliczaniu właśnie tych odległości, a że nieczęsto będzie to liczone, to wolę stracić na czasie. W sumie to już zostawiam tak jak mam, teraz nowy problem mi wyskoczył, może rano po przebudzeniu mnie olśni.
Bags_Bunny
Kod
WHERE i1<i2 AND i1 NOT IN (SELECT id1 from %2$s WHERE id1 = i1 AND id2 = i2)

To nie wygląda najlepiej, a przy Twojej strukturze zapytania z indeksami cięzko. Polecam pozbyć się NOT IN albo zmienić zapytanie tak, żeby nie generowało tymczasowych danych, tylko operowało na istniejących tabelach. i1<i2 też lekkie nie jest.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.