2012-09-19 28 views
4

我有一個MySQL選擇語句在我的網站上搜索,當網站變得非常忙時有性能問題。下面的查詢從超過10萬條記錄的表中搜索廣告,距離給定經緯度和距離不超過25英里,並按距離排序。用戶選擇里程數可能會有所不同。MYSQL地理搜索具有距離性能

問題是,我認爲它很慢,因爲它對錶格中的所有記錄進行了計算,而不是那些距緯度和經度25英里以內的記錄。是否可以修改此查詢,以便where子句僅選擇25英里內的廣告?我讀過關於邊界框和空間索引,但我不知道如何將它們應用於此查詢,我是否需要添加一個where子句來選擇記錄經緯度半徑25英里的記錄,我該怎麼做?

SELECT 
    adverts.*, 
    round(sqrt((((adverts.latitude - '53.410778') * (adverts.latitude - '53.410778')) * 69.1 * 69.1) + ((adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53)), 1) as distance 
FROM 
    adverts 
WHERE 
    (adverts.type_id = '3') 
HAVING 
    DISTANCE < 25 
ORDER BY 
    distance ASC 
LIMIT 120,10 

編輯:更新後包括表模式,請注意該表是比較複雜的,所以是查詢,但我已刪除了這是沒有必要爲這個問題的事情。

CREATE TABLE `adverts` (
`advert_id` int(10) NOT NULL AUTO_INCREMENT, 
`type_id` tinyint(1) NOT NULL, 
`headline` varchar(50) NOT NULL, 
`description` text NOT NULL, 
`price` int(4) NOT NULL, 
`postcode` varchar(7) NOT NULL, 
`latitude` float NOT NULL, 
`longitude` float NOT NULL, 
PRIMARY KEY (`advert_id`), 
KEY `latlon` (`latitude`,`longitude`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 

當我做一個關於mysql語句的行數設置爲67900這是一個很大的比在25英里半徑解釋,還額外設置爲「使用,其中,使用文件排序」 。

查詢需要0.3秒,這非常慢,特別是當網站每秒獲得大量請求時。

+0

我已經注意到這個查詢有幾個問題,我對如何使它更快一些有一些想法,你可以給我們一個表模式的預覽? (你的主鍵等) – Killrawr

回答

3

有幾種方法來加快您的查詢,我個人會利用POW功能。

返回提高到Ÿ電源的X值。

儘管實現了相同的結果,但手動乘法會減慢大表的查詢速度。

SELECT a .* , 
    round(sqrt( 
     (POW(a.latitude -'53.410778', 2)* 68.1 * 68.1) + 
     (POW(a.latitude -'-2.97784', 2) * 53.1 * 53.1) 
    )) AS distance 
FROM adverts a 
    WHERE a.type_id = 3 
    HAVING distance < 25 
    LIMIT 0 , 30 

上面的查詢中0.0008 sec運行在與10,000記錄表的模式(您查詢在同一個表架構測試了0.0129 sec),所以它在性能上的顯着增加。

其它優化技巧

  • 如果您在SELECT語句,而不是*使用實際的列名SQL查詢變得更快。
  • 完全參考表名mydatabase.mytable
  • 如果你有ORDER BY使用primary key(它的一個indexed場,或創建您打算在ORDERING領域的index)。
  • 使用mysql框架函數進行數學計算,可以加快進程。
  • 最後嘗試使這些步驟儘可能簡單(越簡單越快)您的查詢。

來源

+0

謝謝,我不認爲我可以在where子句中引用a.DISTANCE,因爲距離是在select中計算的,它實際上並不是表中的一個字段。 – user1052096

+0

嗨,@dist不起作用,它帶回了一個空的結果集。 – user1052096

+0

在做了更多的閱讀之後,我發現'WHERE Clause'實際上沒有訪問'user-defined'變量的權限。我在適當的地方編輯了我的答案。問候 – Killrawr

5

最快的方法是使用MySQL的地理空間擴展,這應該很容易,因爲您已經使用了MyISAM表。這些擴展的文檔可以在這裏找到:http://dev.mysql.com/doc/refman/5.6/en/spatial-extensions.html

添加新柱,以點數據類型:

ALTER TABLE `adverts` 
ADD COLUMN `geopoint` POINT NOT NULL AFTER `longitude` 
ADD SPATIAL KEY `geopoint` (`geopoint`) 

然後,您可以從現有的緯度和經度領域填充此列:

UPDATE `adverts` 
SET `geopoint` = GeomFromText(CONCAT('POINT(',`latitude`,' ',`longitude`,')')); 

下一步是根據輸入的經度和緯度創建一個邊界框,將在WHERE子句中用作CONTAINS約束條件。您將需要根據所需搜索區域和給定起點確定一組適用於您的要求的X,Y POINT座標。

您最終的查詢將搜索所有POINT數據是搜索POLYGON內,然後就可以使用距離計算,以進一步完善和整理你的數據:

SELECT a.*, 
    ROUND(SQRT((((adverts.latitude - '53.410778') * (adverts.latitude - '53.410778')) * 69.1 * 69.1) + ((adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53)), 1) AS distance 
FROM adverts a 
WHERE a.type_id = 3 
AND CONTAINS(a.geopoint, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))')) 
HAVING distance < 25 
ORDER BY distance DESC 
LIMIT 0, 30 

注意,GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))')在上面將不工作,您將需要用您的搜索開始周圍的有效點替換座標。如果您預計經緯度發生變化,您應該考慮使用觸發器來保持POINT數據和關聯的SPATIAL KEY最新。對於大型數據集,在計算每條記錄的距離和使用HAVING子句進行篩選時,您應該看到性能大大提高。我親自定義了用於確定距離和創建邊界的函數POLYGON

+0

如何使用輸入Lat/Lon定義'Polygon()'?例如。從輸入緯度/經度到每個邊緣的25miles的正方形,或者半徑爲25miles並以輸入緯度/經度爲中心的圓的近似度等。 –

+1

這取決於你想要的準確程度。每個經度的緯度約爲54.6英里,赤道的每個緯度約爲69英里,在兩極接近0。只是谷歌「計算使用經度和緯度的邊界框」的主題文章。 – doublesharp