索引咱們都用過,它是一種特殊的存儲結構,就像圖書館裏書的分類存放策略或是現代化圖書館裏的圖書查詢系統,能幫助咱們快速找到本身須要的書。 數據庫中,索引的存儲通常使用 B樹 或 B+樹 來實現,經過二分法來查找法來快速定位到數據位置。php
普通索引對於一維數據(key->data)是無往不利,但是面對空間數據(lon,lat -> data)就有些無能爲力了,若是查詢(116.27636, 40.041285)附近的點:css
咱們在 lon 或 lat 列上建立普通索引,假設是 lon 列,那麼經過 lon 列查找到同一經度的數據後,還要在此基礎上過濾掉緯度差別過大的數據。html
若是在 lon,lat 上建立多列索引,查詢到同一經度、緯度相近的數據當然快,但附近的點並不僅是經度相同。node
如此下來,就要用到空間索引了。空間索引經過 四叉樹、R 樹等數據結構,還有 GeoHash 算法將二維數據轉化爲一維使用普通B樹索引 來實現,它們都能實現對空間範圍內的快速搜索。mysql
但是,今天的主題不在這裏,咱們的首要目標是要解決問題,這些空間索引的實現改日專門寫文章來實現。本文來講一說現有的數據庫中對空間索引的支持狀況,但願能幫助跟我同樣的 GIS 小白進行技術選型。git
組內準備切換 poi 數據的存儲數據庫,花了一週時間安裝配置各類數據庫來測試空間索引的效率,測試了 Redis, Mongo, PostgreSQL, Mysql 這幾個知名的支持空間索引的數據庫,技術選型基本完畢,但是中間踩過的坑和配置經驗不能丟,詳情以下:github
redis,一個功能強大、效率極高的緩存數據庫(或許已經不只僅是緩存數據庫了),已經成爲相似於關係存儲型數據庫在各個項目中不可或缺的組件了。首先考慮它是由於它的效率有保障,並且項目中幾乎必備,運維代價很低。Redis 的 空間索引採用 GeoHash 原理,配合集合存儲,查詢效率接近 log(N)。web
Redis 3.0 以上版本支持空間索引,新項目沒必要考慮這些,而通常的老項目可能就須要升級 Redis 了,另外 PHP 中可能還要升級 Redis 的擴展,以支持 Redis 的空間索引函數。redis
Redis 的安裝配置這裏就再也不多提了,這裏簡單地介紹一下 Redis 的 GEO 系列函數。算法
GEOADD key longitude latitude member [longitude latitude member ...]
GEOADD 將元素添加到集合中,可一次添加多個元素,其對應的 php 函數原型爲: geoadd($key, $lon, $lat, $member)
;
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]
GEORADIUS 查詢集合內 以目標點爲圓心,半徑爲radius的圓內 的元素。其 php 函數原型爲 georadius($key, $lon, $lat, $radius, $unit, $options)
; 其 $options 相似於 array('count' => $count, 'WITHDIST' ...);
Redis 確實效率高,使用方便,但有一個沒法克服的問題,即沒法實現多條件查詢。僅僅查詢附近的點,Redis 是無懈可擊,可是若是需求是查詢附近的飯店呢?或是需求查詢附近的 '萬達' 呢?
不是不能夠實現:
在關係型數據庫內存儲每一個地點的詳細信息,Redis 內的 member 存儲每一個地點在關係型數據庫中的主鍵 ID,查詢到地點的 ID 後,再去取地點的詳細信息來過濾。
多餘的庫訪問,會形成額外的網絡開銷和 IO 開銷。
在以必定規則拼接 member 的值,如 $memeber = $name.','.$category;
,在查詢到地點後解析 member 後進行過濾。
較上面方法,省了網絡開銷,但不夠靈活,若是再加上'城市'的限制,那麼整個庫的數據都要被清洗。
MongoDB 是老牌的支持空間索引的數據庫,做爲一個文檔型數據庫,它在存儲日誌或靜態數據時效果不錯。 它提供兩種類型的空間索引:
2d 索引 和2dsphere 索引都是使用 GeoHash 算法用 B+ 樹來實現。
Mongo 建立空間索引的方法很簡單:db.collection.createIndex( { field : "2dsphere" } );
。
查詢語句相似(下面是查詢距目標點 3000米 內的地點):
db.poi.find( { loc : { $near : { $geometry : { type : "Point" , coordinates : [ 113.965355, 23.782865] } , $maxDistance : 3000 } } } )
Mongo 的使用須要注意以下:
Mongo 的 PHP 擴展已經更新了,舊的擴展已被廢棄,操做要使用 MongoDB\Driver\XXX 等類來進行,具體方法仍是官方文檔比較清晰。
Mongo 的 2dsphere 索引須要創建索引的字段存儲的數據爲 geoJSON 對象,在 PHP 中的構造形式相似:
$document = [ 'loc' => [ 'type' => 'Point', 'coordinates' => [$lon, $lat], ], 'name' => $name ];
Mongo在查詢返回距離時須要使用 runCommand 命令,其語法相似於 db.runCommand({"geoNear":"collection", "near":[lon, lat], "num":count, query:{other condition}})
;
mongo 的空間索引仍是比較靈活的,GeoJSON 對象有點、線、多邊形、多條線段、多點、多個多邊形。支持 包含、相交、臨近的查詢,同時它也解決了 Redis 的多條件查詢問題。
可是測試發現,mongo 有如下問題:
參考:Mongodb地理空間索引和查詢(Geospatial Indexes)
postgreSQL 是一個知名的關係型數據庫,構建在其上的空間對象擴展模塊 PostGIS 使得其成爲一個真正的大型空間數據庫。它經過 R樹 或 GIST 樹索引來實現共空間索引,查詢效率極高。同時它對分詞模糊查詢支持很好,也能解決以地點名查詢的需求。
PostGIS 是一個開源程序,它爲對象-關係型數據庫PostgreSQL提供了存儲空間地理數據的支持,使 PostgreSQL 成爲了一個空間數據庫,可以進行空間數據管理、數量測量與幾何拓撲分析。PostGIS 實現了 Open Geospatial Consortium 所提出的基本要素類(點、線、面、多點、多線、多面等)的 SQL 實現參考。
postgreSQL 的使用,對比其餘數據庫來講,較繁瑣。
postgres -D datadir
開啓服務;CREATE EXTENSION postgis;
安裝擴展;CREATE INDEX idx_name ON table USING gist(field);
;而後就能夠建表建索引導數據了。
如下是一個典型的查詢語句(查詢跟目標點 3000米 內的地點名稱和距離):
SELECT id, name, st_astext(loc), ST_Distance(loc, ST_GeographyFromText('SRID=4326;POINT(118.08688 33.64843)')) as dist FROM test WHERE ST_DWithin(loc, ST_GeographyFromText('SRID=4326;POINT(118.08688 33.64843)'), 3000) order by dist ASC limit 200;
使用時還須要注意:
-E UTF8
選項來指定字符集;loc geography(point, 4326)
,另外將數據轉爲 geography 點時也要注意使用 SRID:4326;ST_ASText(field)
和 ST_GeographyFromText('SRID=4326;POINT(lon lat)'))
進行geography 和 字符串點進行轉換;serial
,類型於mysql的 int auto increment;\timing on
和 \timing off
來切換是否顯示命令執行時間;postgreSQL 對空間查詢的支持很是靈活,足以支持多種複雜的空間查詢,PostGIS 能計算不一樣投影座標系下的真實空間距離,且查詢效率極高,在大量數據時也不會像 mongo 同樣性能急劇降低。
同時它關係型數據庫的特性支持咱們進行多條件查詢,最後它也可使用 zhparser
擴展來進行中文分詞,以支持對地點名模糊查詢。
雖然它在存在着複雜索引時寫入較慢的問題,但對於存儲不常變更的地點信息來講,是無關大礙的。
參考: PgSQL · 功能分析 · PostGIS 在 O2O應用中的優點
PostgreSQL 全表 全字段 模糊查詢的毫秒級高效實現
Mysql 的重要性和強大沒必要多言,它的存儲引擎 MyISAM 很早就支持空間索引。而 InnoDB 則在5.7.4 labs版本中才添加對空間索引的支持。
它們都是經過 R 樹來實現空間索引。
Mysql 中空間索引使用時要注意:
field geometry NOT NULL
;SPATIAL KEY `idx_fld` (`geom`)
來建立一列空間索引;POINTFROMTEXT('POINT(lon lat)')
;GEOMFROMTEXT('Polygon((lon1 lat1,lon2 lat2,lon3 lat3,lon4 lat4 ...))'
如下是一個典型的空間查詢語句(查詢距目標點3km之內的點):
SELECT id, ST_Distance_Sphere(Point(-73.951368, 40.716743), geom) as dist, tags, ST_AsText(loc) FROM nodes WHERE ST_Contains( ST_MakeEnvelope( Point((-73.951368+(3/111)), (40.716743+(3/111))), Point((-73.951368-(3/111)), (40.716743-(3/111))) ), loc ) ORDER BY dist LIMIT 10
因爲 Innodb 的功能比 MyISAM 強大太多,且事務、行鎖、B+樹索引等功能的不可替代性,這裏再也不討論 MyISAM。
Mysql 的空間索引查詢效率不低。做爲傳統的關係型數據庫,其多條件支持、分詞也都被很好地支持。
雖然對 InnoDB 的空間索引有信心,也略期待,但是對一個長時間存在的系統來講,數據庫版本的升級真正不是一個簡單的事。
參考:MySQL Blog - mysql對GIS空間數據的支持
我以 126萬 poi 數據進行了測試,查詢範圍 3km 內的點(最多取200條)。 系統信息: macos10.12 (x86_64); 內核: 2 GHz Intel Core i5; 內存: 8 GB 1867 MHz LPDDR3;
如下是各數據庫的對比狀況:
數據庫 | 耗時 | 區域查詢 | 多條件支持 | 分詞支持 | 運維複雜度 | 備註 |
---|---|---|---|---|---|---|
redis(3.2.8) | 1-10ms | 不支持 | 不支持 | 不支持 | 低 | 簡單但功能單一 |
mongo(3.4.4) | 10-50ms | 支持 | 支持 | 不支持 | 中 | 結果數據量大時性能降低明顯 |
postgreSQL(9.6.2) | 3-8ms | 支持 | 支持 | 支持 | 中 | 數據寫入較慢 |
mysql(5.7.18 Innodb) | 8-15ms | 支持 | 支持 | 支持 | 低 | 版本升級太困難 |
可能測試操做時有些偏差,有熟練使用這些數據庫的能夠評論交流一下。
數據庫沒有哪一個必定好,只要適合場景便可。
若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關注 。