Pergunta

Estou usando o geokit (acts_as_mappable) em um aplicativo Rails, e o desempenho das pesquisas radiais ou de limites degrada consideravelmente quando há um grande número de modelos (tentei com 1-2 milhões, mas o problema sem dúvida surge antes disso ).

O Geokit faz todos os seus cálculos com base nas colunas lat e lng da tabela (latitude e longitude).Para melhorar o desempenho, o geokit normalmente adiciona uma cláusula 'where' de caixa delimitadora, com a intenção de usar um índice combinado de latitude e longitude para melhorar o desempenho.No entanto, ainda é incrivelmente lento com um grande número de modelos, e parece-me que a cláusula da caixa delimitadora deveria ajudar muito mais do que realmente ajuda.

Então, minha pergunta é: existe uma maneira de fazer o mysql fazer melhor uso do índice combinado lat/lng ou melhorar o desempenho das consultas sql do geokit?Ou o índice combinado para lat/lng pode ser mais útil?

editar:Eu comecei a trabalhar com Rails agora e escrevi a solução com mais detalhes aqui

Mais antecedentes

Por exemplo, esta consulta encontra todos os lugares num raio de 10 milhas de um determinado ponto.(Eu adicionei .length apenas para determinar quantos resultados retornam - há maneiras melhores de dizer isso no geokit, mas eu queria forçar uma consulta SQL mais típica).

Place.find(:all,:origin=>latlng,:within=>10).length

Demora cerca de 14 segundos em um mac mini.Aqui está o plano de explicação

mysql> explain SELECT *, (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  AS distance FROM `places` WHERE (((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) AND ( (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+
    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+
    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  <= 10)) 
    -> ;
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
| id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

Portanto, o mysql está examinando 87.554 linhas, embora o número de casas no resultado seja 1.135 (e o número de casas realmente na caixa delimitadora seja apenas 1.323).

Estas são as estatísticas do índice (que é feito com uma migração Rails add_index :lugares, [:lat, :lng]):

| Table  | Non_unique | Key_name                         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
| places |          1 | index_places_on_lat_and_lng      |            2 | lng              | A         |     1373712 |     NULL | NULL   | YES  | BTREE      |         |

Nem parece estar relacionado aos cálculos trigonométricos, pois fazer uma consulta semelhante para uma caixa delimitadora resulta em uma consulta muito mais simples, mas tem um desempenho igualmente ruim:

Place.find(:all,:bounds=>GeoKit::Bounds.from_point_and_radius(latlng,10)).length

Fornece um plano de explicação semelhante:

   mysql> explain SELECT * FROM `places` WHERE ((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) ;
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    | id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    |  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
Foi útil?

Solução

Simples B-Tree índices não são muito bons para consultas como esta.

Para sua consulta, o range O método de acesso é usado na seguinte condição:

places.lat > 51.3373601471464 AND places.lat < 51.6264998528536

, isso nem leva lon em conta.

Se você quiser usar habilidades espaciais, você deve manter seus lugares como Points, Crie um SPATIAL índice deles e uso MBRContains para filtrar a caixa delimitadora:

ALTER TABLE places ADD place_point GEOMETRY

CREATE SPATIAL INDEX sx_places_points ON places (place_point)

UPDATE  places
SET     place_point = Point(lat, lon)

SELECT  *
FROM    places
WHERE   MBRContains(LineString(Point(51.3373, -1.1330), Point(51.6264, -0.6687)), place_point)
        AND -- do the fine filtering here

Atualizar:

CREATE TABLE t_spatial (id INT NOT NULL, lat FLOAT NOT NULL, lon FLOAT NOT NULL, coord GEOMETRY) ENGINE=MyISAM;

INSERT
INTO    t_spatial (id, lat, lon)
VALUES  (1, 52.2532, 20.9778);

UPDATE  t_spatial
SET     coord = Point(lat, lon);

Isso funciona para mim em 5.1.35.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top