2011-11-23 12 views
11

Mam dwie tabele: jedną z punktami, drugą z polisami.Przystawki na przestrzennych wskaźnikach mysql

CREATE TABLE `points` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `point` point NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM; 

CREATE TABLE `ranges` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `poly` polygon NOT NULL, 
    PRIMARY KEY (`id`), 
    SPATIAL KEY `poly` (`poly`) 
) ENGINE=MyISAM; 

Chcę dołączyć zakresy do punktów w punktach wewnątrz polys. Zapytania wyglądać prosta:

SELECT * 
    FROM points 
    LEFT JOIN ranges 
     ON MBRCONTAINS(poly, point) 
    WHERE points.id = 2; 

Ta kwerenda działa szybko i wykorzystuje indeksy część wyjaśnić:

table | type | possible_keys | key | key_len 
ranges | range | poly   | poly | 34 

ale gdy próbuję połączyć się z kilku wierszy z tabeli points:

SELECT * 
    FROM points 
    LEFT JOIN ranges 
    ON MBRCONTAINS(poly, point) 
    WHERE points.id IN (1,2,3); 

wszystko ulega awarii:

+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref | rows | Extra  | 
+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ 
| 1 | SIMPLE  | points  | range | PRIMARY  | PRIMARY | 4  | NULL |  3 | Using where | 
| 1 | SIMPLE  | ranges  | ALL | poly   | NULL | NULL | NULL | 155183 |    | 
+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ 

Dodawanie FORCE INDEX (poly) nie pomaga.

Przykładowe dane do testowania zapytań (przepraszam, tylko wersja php, nie jestem wspólnego z procedur SQL):

//points 
for($i=0;$i<=500;$i++) { 
    $point = mt_rand(); 
    mysql_query('INSERT INTO points (point) VALUES (POINTFROMWKB(POINT('.$point.', 0)))'); 
} 

$qty = 20000; 
$max = mt_getrandmax(); 
$add = $max/$qty 
$end = 0; 

//polys 
while($end < $max) { 
    $start = $end; 
    $end = mt_rand($start, $start + $add); 
    mysql_query('INSERT INTO ranges (poly) VALUES (
     GEOMFROMWKB(POLYGON(LINESTRING(
      POINT('.$start.', -1), 
      POINT('.$end.', -1), 
      POINT('.$end.', 1), 
      POINT('.$start.', 1), 
      POINT('.$start.', -1) 
     ))) 
    )'); 
} 
+0

http://dba.stackexchange.com/? –

+0

Czy możesz wyjaśnić, co masz na myśli mówiąc "wszystko się psuje"? Komunikat o błędzie jest trudny do odczytania ?! – Bytemain

+0

Czy chciałbyś opublikować przykładową wstawkę dla tych tabel i Czy próbowałeś przepisać ten warunek IN, aby używać Połączeń? – Pentium10

Odpowiedz

6

wierzę, że to dlatego, że MySQL nie obsługuje scalania indeksów przestrzennych. Nie jestem pewien, czy to nadal prawda, ale przeczytałem ją gdzieś w przeszłości. Jeśli masz instrukcję OR, to przestrzenne indeksy nie są używane

W twoim przypadku, gdzie robisz points.id = 1, to jest prosty wybór z jednym zwróconym wynikiem, który zostanie użyty w mbrcontains. To wykorzystuje indeks.

Po dodaniu points.in (1,2,3), który zwraca 3 wyników i każdy musi być mapowane do tabeli zakresy, dlatego nie działa

wynik

id select_type  table type possible_keys key  key_len  ref  rows filtered Extra 
1 SIMPLE points range PRIMARY  PRIMARY  4 NULL 3 100.00 Using where 
1 SIMPLE ranges ALL  poly NULL NULL NULL 6467418  100.00 

Można uprościć test bez tabeli punktowej w ten sposób: select * from zakresach gdzie mbrcontains (poli, GEOMFROMWKB (POINT (0, 0)))

id select_type  table type possible_keys key  key_len  ref  rows filtered Extra 
1 SIMPLE ranges range poly poly 34 NULL 1 100.00 Using where 

A teraz; SELECT * znad gdzie mbrcontains (poli, GEOMFROMWKB (PKT (0, 0))) lub mbrcontains (poli, GEOMFROMWKB (PKT (10, 10)))

wynik

id select_type  table type possible_keys key  key_len  ref  rows filtered Extra 
1 SIMPLE ranges ALL  poly NULL NULL NULL 6467418  100.00 Using where 

zobaczyć, że w W drugim przypadku nie korzystasz z indeksu i po prostu skanujesz.

Można wymusić zapytanie, aby użyć indeksu, tworząc UNION dla każdego konkretnego punktu, ale nie jestem pewien, czy to będzie szybsze. Zrobiłem kilka testów lokalnie i było nieco wolniej niż twoje pierwsze zapytanie.

EXPLAIN EXTENDED 
SELECT * 
FROM points 
FORCE INDEX (PRIMARY) 
LEFT JOIN ranges 
FORCE INDEX (poly) ON mbrcontains(poly, point) 
WHERE points.id = 1 
UNION DISTINCT 
SELECT * 
FROM points 
FORCE INDEX (PRIMARY) 
LEFT JOIN ranges 
FORCE INDEX (poly) ON mbrcontains(poly, point) 
WHERE points.id = 2 
UNION DISTINCT 
SELECT * 
FROM points 
FORCE INDEX (PRIMARY) 
LEFT JOIN ranges 
FORCE INDEX (poly) ON mbrcontains(poly, point) 
WHERE points.id = 3 

wynik

id select_type  table type possible_keys key  key_len  ref  rows filtered Extra 
1 PRIMARY  points const PRIMARY  PRIMARY  4 const 1 100.00 
1 PRIMARY  ranges range poly poly 34 NULL 1 100.00 Using where 
2 UNION points const PRIMARY  PRIMARY  4 const 1 100.00 
2 UNION ranges range poly poly 34 NULL 1 100.00 Using where 
3 UNION points const PRIMARY  PRIMARY  4 const 1 100.00 
3 UNION ranges range poly poly 34 NULL 1 100.00 Using where 
NULL UNION RESULT <union1,2,3> ALL  NULL NULL NULL NULL NULL NULL  
+1

@ oroboros102, znalazłem odnośnik na wszelki wypadek: http://dev.mysql.com/doc/refman/5.0/en/range-optimization.html. Ostatni akapit w 7.3.1.3.1. –

0

Jeśli wszystko masz do czynienia są kwadratami, chciałbym po prostu do czynienia z 4 liczb w tabeli, które mogą być indeksowane reprezentujący górny, lewy, wysokość, szerokość, a następnie uruchomić zapytanie, w którym twój punkt ma współrzędną "X" pomiędzy lewą, lewą i tylną i współrzędną "Y" między górą, górą i górą.

+0

Ale używam geometrii, ponieważ jest znacznie szybsza niż "BETWEEN". – Oroboros102

+0

Rzeczywiście, MySQL nie może dołączyć do 'BETWEEN'. Miałem więc pomysł zrobienia takich sprzężeń na indeksach przestrzennych. Ale to się nie udało. – Oroboros102

3

Z powodzeniem używam podobnych zapytań, z jedną różnicą w modelu danych: klucz przestrzenny w bazie punktów. W moim przypadku:

CREATE TABLE geopoints (
    pid int(11) NOT NULL AUTO_INCREMENT, 
    description varchar(255) NOT NULL DEFAULT '', 
    geopoint point NOT NULL, 
    PRIMARY KEY (pid), 
    SPATIAL KEY geopoint (geopoint) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

i wszystko poszło dobrze w zapytaniach tak:

SELECT pt.pid, x(geopoint), Y(geopoint), pl.pid, AsText(geopolygon) 
    FROM geopoints pt INNER JOIN geopolygons pl ON MBRCONTAINS(geopolygon, geopoint) 
WHERE pt.pid IN (1,2,4,5) AND pl.pid BETWEEN 1 AND 5; 

moich dwóch centów,

Powiązane problemy