1. 程式人生 > >空間索引 - 各數據庫空間索引使用報告

空間索引 - 各數據庫空間索引使用報告

官方文檔 分類 vertical posit 大量 cti 3.2 opacity 權限

空間索引

索引我們都用過,它是一種特殊的存儲結構,就像圖書館裏書的分類存放策略或是現代化圖書館裏的圖書查詢系統,能幫助我們快速找到自己需要的書。 數據庫中,索引的存儲一般使用 B樹 或 B+樹 來實現,通過二分法來查找法來快速定位到數據位置。

普通索引對於一維數據(key->data)是無往不利,可是面對空間數據(lon,lat -> data)就有些無能為力了,如果查詢(116.27636, 40.041285)附近的點:

  • 我們在 lon 或 lat 列上創建普通索引,假設是 lon 列,那麽通過 lon 列查找到同一經度的數據後,還要在此基礎上過濾掉緯度差異過大的數據。

  • 如果在 lon,lat 上創建多列索引,查詢到同一經度、緯度相近的數據固然快,但附近的點並不只是經度相同。

如此下來,就要用到空間索引了。空間索引通過 四叉樹、R 樹等數據結構,還有 GeoHash 算法將二維數據轉化為一維使用普通B樹索引 來實現,它們都能實現對空間範圍內的快速搜索。

可是,今天的主題不在這裏,我們的首要目標是要解決問題,這些空間索引的實現改日專門寫文章來實現。本文來說一說現有的數據庫中對空間索引的支持情況,希望能幫助跟我一樣的 GIS 小白進行技術選型。

組內準備切換 poi 數據的存儲數據庫,花了一周時間安裝配置各種數據庫來測試空間索引的效率,測試了 Redis, Mongo, PostgreSQL, Mysql 這幾個知名的支持空間索引的數據庫,技術選型基本完畢,可是中間踩過的坑和配置經驗不能丟,詳情如下:


Redis

介紹

redis,一個功能強大、效率極高的緩存數據庫(或許已經不僅僅是緩存數據庫了),已經成為類似於關系存儲型數據庫在各個項目中不可或缺的組件了。首先考慮它是因為它的效率有保障,而且項目中幾乎必備,運維代價很低。Redis 的 空間索引采用 GeoHash 原理,配合集合存儲,查詢效率接近 log(N)。

Redis 3.0 以上版本支持空間索引,新項目不必考慮這些,而一般的老項目可能就需要升級 Redis 了,另外 PHP 中可能還要升級 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 後進行過濾。

    較上面方法,省了網絡開銷,但不夠靈活,如果再加上‘城市‘的限制,那麽整個庫的數據都要被清洗。

參考: Redis 命令參考 ? GEO(地理位置)


MongoDB

介紹

MongoDB 是老牌的支持空間索引的數據庫,作為一個文檔型數據庫,它在存儲日誌或靜態數據時效果不錯。 它提供兩種類型的空間索引:

  • 2d 索引支持平臺普通坐標的索引,適用於 2.4 版本之前;我們就不再考慮了,在大範圍上存儲和計算時,效率會有較大誤差。
  • 2dsphere 索引支持查詢在一個類地球的球面上進行幾何計算,以GeoJSON對象或者普通坐標對的方式存儲數據。

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 有以下問題:

  • 在進行大量數據時,性能會急劇下降,特別在符合條件的結果很多時,查詢時間簡直沒法看。
  • Mongo 對分詞模糊查詢的支持不太好,要進行按地點名字模糊查詢還需要想辦法。
  • Mongo 的安全性配置是個問題。

參考:Mongodb地理空間索引和查詢(Geospatial Indexes)

MongoDB ? GeoJSON


PostgreSQL

介紹

postgreSQL 是一個知名的關系型數據庫,構建在其上的空間對象擴展模塊 PostGIS 使得其成為一個真正的大型空間數據庫。它通過 R樹 或 GIST 樹索引來實現共空間索引,查詢效率極高。同時它對分詞模糊查詢支持很好,也能解決以地點名查詢的需求。

PostGIS 是一個開源程序,它為對象-關系型數據庫PostgreSQL提供了存儲空間地理數據的支持,使 PostgreSQL 成為了一個空間數據庫,能夠進行空間數據管理、數量測量與幾何拓撲分析。PostGIS 實現了 Open Geospatial Consortium 所提出的基本要素類(點、線、面、多點、多線、多面等)的 SQL 實現參考。

使用

postgreSQL 的使用,對比其他數據庫來說,較繁瑣。

  1. 要使用 postgreSQL 的空間索引,需要安裝 postgis,由於它依賴多而復雜,能使用 yum,apt-get,homebrew 等工具的優先使用;
  2. 數據庫完畢後使用 initdb 命令初始化一個數據庫;
  3. 使用非root用戶 postgres -D datadir 開啟服務;
  4. 使用 CREATE EXTENSION postgis; 安裝擴展;
  5. 使用 CREATE INDEX idx_name ON table USING gist(field);

然後就可以建表建索引導數據了。

以下是一個典型的查詢語句(查詢跟目標點 3000米 內的地點名稱和距離):

SELECT 
    name, ST_Distance(loc, ST_GeomFromText(‘POINT(113.2242 23.323234)‘, 4326)) as dist 
from test_table WHERE 
    ST_DWithin(loc, ST_GeomFromText(‘POINT(113.2242 23.323234)‘, 4326), 0.03) 
ORDER BY dist limit 200

使用時還需要註意:

  • 如果需要進行中文分詞查詢的話,初始化數據庫時要添加 -E UTF8 選項來指定字符集;
  • postgreSQL 不能使用 root 用戶登陸,對於權限的控制也比較嚴,動轍需要賦予權限;
  • 建表時要指定其 SRID (空間參考標識符, 是與特定坐標系、容差和分辨率關聯的唯一標識符) 值,以經緯度存儲用 4326; 例如 loc geometry(point, 4326),另外將數據轉為 gometry 點時也要註意使用 SRID:4326;
  • 主鍵索引數據類型可指定為 serial,類型於mysql的 int auto increment;
  • 使用 \timing on\timing off來切換是否顯示命令執行時間;

結論

postgreSQL 對空間查詢的支持非常靈活,足以支持多種復雜的空間查詢,PostGIS 能計算不同投影坐標系下的真實空間距離,且查詢效率極高,在大量數據時也不會像 mongo 一樣性能急劇下降。

同時它關系型數據庫的特性支持我們進行多條件查詢,最後它也可以使用 zhparser 擴展來進行中文分詞,以支持對地點名模糊查詢。

雖然它在存在著復雜索引時寫入較慢的問題,但對於存儲不常變動的地點信息來說,是無關大礙的。

參考: PgSQL · 功能分析 · PostGIS 在 O2O應用中的優勢

PostgreSQL 全表 全字段 模糊查詢的毫秒級高效實現


MySQL

介紹

Mysql 的重要性和強大不必多言,它的存儲引擎 MyISAM 很早就支持空間索引。而 InnoDB 則在5.7.4 labs版本中才添加對空間索引的支持。

它們都是通過 R 樹來實現空間索引。

使用

Mysql 中空間索引使用時要註意:

  • 對空間索引的字段首先要設置為field geometry NOT NULL
  • 使用建立空間索引 SPATIAL KEY `idx_fld` (`geom`)來創建一列空間索引;
  • SQL語句中字符串與geometry的轉換函數 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 支持 支持 支持 版本升級太困難

可能測試操作時有些誤差,有熟練使用這些數據庫的可以評論交流一下。

數據庫沒有哪個一定好,只要適合場景即可。

如果您覺得本文對您有幫助,可以點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關註

空間索引 - 各數據庫空間索引使用報告