計(jì)算兩點(diǎn)之間的距離

早在《高性能MySQL》上看到了,一直想記錄下來(lái),我有嚴(yán)重拖延癥。。。最近公司在這個(gè)類(lèi)似業(yè)務(wù)方面需要優(yōu)化,所以趕緊再仔細(xì)看一下,記錄下來(lái),加深一下印象。

此文章參考來(lái)源《高性能MySQL》6.8.2節(jié),有興趣可以去看一下。

解決的問(wèn)題

很多業(yè)務(wù)有查找某個(gè)點(diǎn)附近的人,附近的商戶(hù)等等。

準(zhǔn)備

創(chuàng)建保存用戶(hù)位置的表

CREATE TABLE locations (
  id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(30),
  lat FLOAT NOT NULL CONTENT '緯度',
  lon FLOAT NOT NULL CONTENT '經(jīng)度'
);

最初方案

我們假設(shè)地球是圓的,然后使用兩點(diǎn)所在最大元圓公式來(lái)計(jì)算兩點(diǎn)之間的距離。

球面距離公式是計(jì)算球面上兩點(diǎn)間距離的公式。設(shè)所求點(diǎn)A ,緯度β1 ,經(jīng)度α1 ;點(diǎn)B ,緯度β2 ,經(jīng)度α2。
則距離S=R·arccos[cosβ1cosβ2cos(α1-α2)+sinβ1sinβ2],其中R為球體半徑。

以上公式R是地球的半徑,如果去掉R則是兩點(diǎn)之間的弧度。如果直接按照這種方法計(jì)算兩點(diǎn)之間的距離,則計(jì)算公式如下:

---RADIANS(X):返回X從度轉(zhuǎn)換成弧度的值 
SELECT * FROM locations 
WHERE 3979 * ACOS(
COS(RADIANS(lat))*COS(RADIANS(38.03))*COS(RADIANS(lon)-RADIANS(-78.48)) 
+ SIN(RADIANS(lat))*SIN(38.03))<=100;

缺點(diǎn):這類(lèi)查詢(xún)不僅無(wú)法使用索引,而且還會(huì)非常消耗CPU時(shí)間,給數(shù)據(jù)庫(kù)帶來(lái)很大的壓力。
思考:也許我們不需要這么精確的計(jì)算,其實(shí)本身地址已經(jīng)很不精確,再加上沒(méi)有正在直接的距離。地球確切的說(shuō)也不是圓的。

改進(jìn)方案

如果我們不用圓周,而是用正方形,如圖(隨便看看,下圖是圓周,把那個(gè)地方改成正方形):

image.png

根據(jù)正方形公式來(lái)計(jì)算,公式為l(弧度)=α(圓心角弧度數(shù))*r(半徑),地球半徑為6371km=3959英里,所以弧度為100/3959=0.0253(100英里)的中心到邊長(zhǎng)的距離:

-----DEGREES(X):返回X從弧度轉(zhuǎn)換為度值----
SWLECT * FROM locations 
WHERE lat BETWEEN 38.03-DEGREES(0.0253) AND 38.03 +DEGREES(0.0253) 
AND 
lon  BETEEEN -78.48-DEGREES(0.0253) AND -78.48 +DEGREES(0.0253)

然而這樣就不能使用索引了(這里不考慮建兩個(gè)索引的情況),因?yàn)閮蓚€(gè)查詢(xún)都是都是范圍的。但是我們可以使用IN()優(yōu)化,我們先新增兩個(gè)列,用來(lái)存儲(chǔ)坐標(biāo)的近似值FLOOR(),然后在查詢(xún)中使用IN()講所有點(diǎn)的整數(shù)值都放到列表中。

索引優(yōu)化

下面是我們需要新增的列和索引:

ALTER TABLE locations ADD lat_floor INT NOT NULL DEFAULT 0,
ADD lon_floor INT NOT NULL DEFAULT 0,
ADD KEY(lat_floor,lon_floor);
------更新這兩列的值
UPDATE locations SET lat_floor= FLOOR(lat), lon_floor = FLOOR(lon);

現(xiàn)在可以根據(jù)近似值來(lái)計(jì)算了,以下的計(jì)算可以在程序中進(jìn)行,限定了經(jīng)緯度的范圍

-----CEILING(X):返回的最小整數(shù)值不小于X;
-----FLOOR(X):返回的最大整數(shù)但不大于X的值;
SELECT FLOOR(38.03-DEGREES(0.0253)) AS lat_lb,
CEILING(38.03+DEGREES(0.0253)) AS lat_ub,
FLOOR(-78.48-DEGREES(0.0253)) AS lon_lb,
CEILING(-78.48+DEGREES(0.0253)) AS lon_ub;
------結(jié)果為36 40 -80 -77

加上索引的sql

SWLECT * FROM locations 
WHERE lat BETWEEN 38.03-DEGREES(0.0253) AND 38.03 +DEGREES(0.0253) 
AND 
lon  BETEEEN -78.48-DEGREES(0.0253) AND -78.48 +DEGREES(0.0253)
AND lat_floor IN(36,37,38,39,40) AND lon_floor IN(-80,-79,-78,-77);

這樣就可以使用索引了,并且速度也很快了。如果希望精度再高些,則可以使用第一個(gè)sql再加上IN查詢(xún)就可以了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容