1. 程式人生 > >MySQL根據經緯度查詢最近距離

MySQL根據經緯度查詢最近距離

A點經緯度:x1,y1  B點經緯度x2,y2

計算公式:

距離可以用r*arccos[cos(y1)*cos(y2)*cos(x1-x2)+sin(y1)*sin(y2)]來算
r是地球半徑6370km,x是經度,y是緯度

之前很啥很天真地以為無非就是逐個計算距離,然後比較出來就行了,然後當碰到訪問使用者很多,而且資料庫中經緯度資訊很多的時候,計算量的迅速增長,能讓伺服器完全傻逼掉,還是老前輩的經驗比我們豐富,給了我很大的啟示。

sql語句查詢經緯度範圍

指定一個經緯度,給定一個範圍值(單位:千米),查出在經緯度周圍這個範圍內的資料。 
經度:113.914619 
緯度:22.50128 
範圍:2km 
longitude為資料表經度欄位 
latitude為資料表緯度欄位 
SQL在

mysql下測試通過,其他資料庫可能需要修改 
SQL語句如下: 
select * from location where sqrt( ( ((113.914619-longitude)*PI()*12656*cos(((22.50128+latitude)/2)*PI()/180)/180) * ((113.914619-longitude)*PI()*12656*cos (((22.50128+latitude)/2)*PI()/180)/180) ) + ( ((22.50128-latitude)*PI()*12656/180) * ((22.50128-latitude)*PI()*12656/180) ) )<2

MySQL效能調優 – 使用更為快速的演算法進行距離

最近遇到了一個問題,通過不斷的嘗試最終將某句原本佔據近1秒的查詢優化到了0.01秒,效率提高了100倍.

問題是這樣的,有一張存放使用者居住地點經緯度資訊的MySQL資料表,表結構可以簡化 為:id(int),longitude(long),latitude()long. 而業務系統中有一個功能是查詢離某個使用者最近的其餘數個使用者,通過程式碼分析,可以確定原先的做法基本是這樣的:

//需要查詢的使用者的座標

$lat=20; $lon=20;//執行查詢,算出該使用者與所有其他使用者的距離,取出最近的10個 $sql='select * from users_location order by ACOS(SIN((
'.$lat.' * 3.1415) / 180 ) *SIN((latitude * 3.1415) / 180 ) +COS(('.$lat.' * 3.1415) / 180 ) * COS((latitude * 3.1415) / 180 ) *COS(('.$lon.' * 3.1415) / 180 - (longitude * 3.1415) / 180 ) ) * 6380 asc limit 10';

而這條sql執行的速度卻非常緩慢,用了近1秒的時間才返回結果,應該是因為order裡的子語句用了太多的數學計算公式,導致整體的運算速度下降.

而在實際的使用中,不太可能會發生需要計算該使用者與所有其他使用者的距離,然後再排序的情況,當用戶數量達到一個級別時,就可以在一個較小的範圍裡進行搜尋,而非在所有使用者中進行搜尋.

所以對於這個例子,我增加了4個where條件,只對於經度和緯度大於或小於該使用者1度(111公里)範圍內的使用者進行距離計算,同時對資料表中的經度和緯度兩個列增加了索引來優化where語句執行時的速度.

最終的sql語句如下

$sql='select * from users_location where latitude > '.$lat.'-1 and latitude < '.$lat.'+1 and longitude > '.$lon.'-1 and longitude < '.$lon.'+1 order by ACOS(SIN(('.$lat.' * 3.1415) / 180 ) *SIN((latitude * 3.1415) / 180 ) +COS(('.$lat.' * 3.1415) / 180 ) * COS((latitude * 3.1415) / 180 ) *COS(('.$lon.'* 3.1415) / 180 - (longitude * 3.1415) / 180 ) ) * 6380 asc limit 10';


經過優化的sql大大提高了執行速度,在某些情況下甚至有100倍的提升.這種從業務角度出發,縮小sql查詢範圍的方法也可以適用在其他地方.

全域性儲存方法的sql

set global log_bin_trust_function_creators=1;
DELIMITER

CREATEDEFINER=CURRENTUSERFUNCTIONfngetDistanceInKilometers(lon1float,lat1float,lon2float,lat2float,rangeint)RETURNSdoublebegindeclareddouble;declareradiusint;setradius=6378140;setd=(2ATAN2(SQRT(SIN((lat1lat2)PI()/180/2)SIN((lat1lat2)PI()/180/2)+COS(lat2PI()/180)COS(lat1PI()/180)SIN((lon1lon2)PI()/180/2)SIN((lon1lon2)PI()/180/2)),SQRT(1SIN((lat1lat2)PI()/180/2)SIN((lat1lat2)PI()/180/2)+COS(lat2PI()/180)COS(lat1PI()/180)SIN((lon1lon2)PI()/180/2)SIN((lon1lon2)PI()/180/2))))radius;returntruncate(d/1000,1);END