1. 程式人生 > >LBS中從資料庫查詢某經緯度2KM範圍內的資料

LBS中從資料庫查詢某經緯度2KM範圍內的資料

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

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查詢範圍的方法也可以適用在其他地方.

正確的計算距離公式是這樣的:

public static double getDistance(double lat1, double lon1, double lat2, double lon2){
double radLat1 = lat1 * Math.PI / 180;
double radLat2 = lat2 * Math.PI / 180;
double a = radLat1 - radLat2;
double b = lon1 * Math.PI / 180 - lon2 * Math.PI / 180;
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2)
* Math.pow(Math.sin(b / 2), 2)));
s = s * 6378137.0;// 取WGS84標準參考橢球中的地球長半徑(單位:m)
s = Math.round(s * 10000) / 10000;

return s;
}