2011-11-23 9 views
11

Ho due tabelle: una con punti, l'altra con polys.Join su indici mysql spaziali

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; 

Desidero unire intervalli a punti su punti all'interno di poligoni. Le query sembrano semplici:

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

Questa query funziona veloce e utilizza gli indici, parte di spiegare:

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

Ma, quando provo ad unire con più righe dalla tabella points:

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

tutto si rompe:

+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ 
| 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 |    | 
+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ 

L'aggiunta di FORCE INDEX (poly) non aiuta.

dati campione per testare le query (mi dispiace, solo la versione PHP, io non sono comuni con le procedure 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

Puoi spiegare cosa intendi con "tutto si rompe"? Il messaggio di errore è difficile da leggere ?! – Bytemain

+0

Sarebbe gentile pubblicare un inserto di esempio per quelle tabelle, e hai provato a riscrivere quella condizione IN per usare Joins? – Pentium10

risposta

6

Credo che sia perché MySQL non supporta la fusione indici spaziali. Non sono sicuro se è ancora vero ma l'ho letto da qualche parte nel passato. Se si dispone di un'istruzione OR, poi gli indici spaziali non vengono utilizzati

Nel tuo caso, in cui stai facendo points.id = 1, che è un diritto di selezione con un solo risultato restituito che si abitua nelle mbrcontains. Questo usa l'indice.

Quando si aggiunge points.in (1,2,3), che restituisce 3 risultati e ciascuno deve essere mappato al tavolo gamme, quindi non lavora

risultato

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 

Puoi semplificare il test senza la tabella punto in questo modo: SELECT * FROM gamme dove mbrcontains (poli, GEOMFROMWKB (pUNTO (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 

E ora questo; SELECT * FROM intervalli dove mbrcontains (poli, GEOMFROMWKB (PUNTO (0, 0))) OR mbrcontains (poli, GEOMFROMWKB (PUNTO (10, 10)))

risultato

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 

vedere che nel secondo caso, non stai usando l'indice e solo la scansione.

È possibile forzare la query all'utilizzo dell'indice creando UNION per ogni punto specifico ma non sono sicuro che sarà più veloce. Ho fatto alcuni test localmente ed è stato un po 'più lento della tua prima query.

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 

risultato

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, trovato il riferimento nel caso in cui lo si desidera: http://dev.mysql.com/doc/refman/5.0/en/range-optimization.html. Ultimo paragrafo in 7.3.1.3.1. –

0

Se tutto quello che a che fare con sono piazze, vorrei solo che fare con 4 numeri della tabella che possono essere indicizzati in rappresentanza superiore, sinistro, Altezza, Larghezza, quindi eseguire la vostra interrogare dove il punto in questione ha la sua coordinata "X" tra sinistra, sinistra + larghezza e coordinata "Y" tra alto, alto + alto.

+0

Ma io uso la geometria, perché è molto più veloce di "BETWEEN". – Oroboros102

+0

In realtà, MySQL non può unirsi su 'BETWEEN'. Quindi, ho avuto l'idea di fare tali join su indici spaziali. Ma, ancora, questo non ha funzionato. – Oroboros102

3

Ho utilizzato con successo query simili, con una sola differenza nel modello di dati: una chiave spaziale nel database dei punti. Nel mio caso:

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; 

e tutto è andato bene nelle query come questo:

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; 

miei due centesimi,