空间索引 - Redis/MongoDB/PostgreSQL/MySQL空间索引使用报告
最近接触到了Mysql空间索引,网上查了查资料,这里记录一下。
空间索引
索引我们都用过,它是一种特殊的存储结构,就像图书馆里书的分类存放策略或是现代化图书馆里的图书查询系统,能帮助我们快速找到自己需要的书。 数据库中,索引的存储一般使用 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 的使用,对比其他数据库来说,较繁琐。
- 要使用 postgreSQL 的空间索引,需要安装 postgis,由于它依赖多而复杂,能使用 yum,apt-get,homebrew 等工具的优先使用;
- 数据库完毕后使用 initdb 命令初始化一个数据库;
- 使用非root用户
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 distFROM test WHERE ST_DWithin(loc, ST_GeographyFromText('SRID=4326;POINT(118.08688 33.64843)'), 3000)
order by dist ASC limit 200;
使用时还需要注意:
- 如果需要进行中文分词查询的话,初始化数据库时要添加
-E UTF8
选项来指定字符集; - postgreSQL 不能使用 root 用户登陆,对于权限的控制也比较严,动辙需要赋予权限;
- 注意将数据保存为 geography 对象,默认使用 m 为单位。在使用 geometry 对象时,默认使用 笛卡尔度 为单位,虽然可以互相转换,但麻烦是少不了的。
- 建表时要指定其 SRID (空间参考标识符, 是与特定坐标系、容差和分辨率关联的唯一标识符) 值,以经纬度存储用 4326; 例如
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
介绍
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空间数据的支持
Mysql 空间索引
本文主要根据mysql 8.0的文档翻译总结,如果使用的是mysql 5.7版本,可能会有些许差异
在涉及LBS的服务开发过程中,经常需要存储地理空间的位置并进行一定计算(附近商家等需求),本文主要介绍mysql对于LBS的支持。
Mysql的空间扩展主要提供一下几个方面的功能:
- 表示空间数值的数据类型
- 操作空间数值的函数
- 空间索引,用于提供访问空间列的速度
其中前两点对InnoDB,MyISAM,NDB,ARCHIVE等mysql存储引擎都支持,第三点只有对InnoDB和MyISAM的支持,由于InnoDB的支持行锁以及事务的特性,现在基本上已经是默认存储引擎了,所以本文以下内容都默认使用InnoDB。
创建空间列以及空间索引的语句如下:
CREATE TABLE geom (g GEOMETRY NOT NULL SRID 4326, SPATIAL INDEX(g));
Mysql空间数据类型
Mysql的空间数据类型与OpenGIS的数据类型相对应。
单一几何值的空间数据类型:
- GEOMETRY
- POINT
- LINESTRING
- POLYGON
其中GEOMETRY可以存储任意类型的集合类型,POINT LINESTRING POLYGON则限制了集合类型
空间集合数据类型:
- MULTIPOINT
- MULTILINESTRING
- MULTIPOLYGON
- GEOMETRYCOLLECTION
空间数据类型的表示形式
Mysql的空间数据有不同表示格式,其中咱能看懂的也就第一种
Well-known Text Format (WKT)形如 Point(1 1)
Well-know Binary Format (WKB) 二进制表示,贴下帧结构,自己感受下
Component Size Value Byte order 1 byte 01
WKB type 4 bytes 01000000
X coordinate 8 bytes 000000000000F03F
Y coordinate 8 bytes 000000000000F0BF
Mysql内部几何存储结构
就是在WKB的前面加上4个字节来表示SRID,就变成了mysql存储的数据结构
什么是SRID
因为上文提到了SRID,这里说下什么是SRID,SR是指Spatial Reference,也就是我们常说的空间参考系,mysql支持卡迪尔坐标系和地理坐标系,其中地理坐标系又有好多种,下面说几种常用的空间参考系
SRID=0
表示一个无限的卡迪尔坐标系平面,且坐标轴上无单位SRID=4326
表示GPS坐标系SRID=3857
是web地图投影,就是你在谷歌地图上看到的坐标系
ALTER TABLE geom ADD position POINT SRID 4326
Mysql的所有空间坐标系都存在表mysql.st_spatial_reference_system
中,这个表是隐藏的,看不见的,但是你可以通过infomation_shcema.st_spatial_reference_system
中查看参考系的信息,这个表就是mysql.st_spatial_reference_system
的一个视图的实现。
mysql> select * from information_schema.st_spatial_reference_systems where srs_id=4326;
+----------+--------+--------------+--------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
| SRS_NAME | SRS_ID | ORGANIZATION | ORGANIZATION_COORDSYS_ID | DEFINITION | DESCRIPTION |
+----------+--------+--------------+--------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
| WGS 84 | 4326 | EPSG | 4326 | GEOGCS["WGS 84",DATUM["World Geodetic System 1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.017453292519943278,AUTHORITY["EPSG","9122"]],AXIS["Lat",NORTH],AXIS["Lon",EAST],AUTHORITY["EPSG","4326"]] | NULL |
+----------+--------+--------------+--------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
1 row in set (0.00 sec)
SRS的解析是在GIS函数调用后才会去懒加载,并把解析的地理位置定义缓存到数据字典中,以后每次需要SRS的信息时不会重复解析
操作空间数据
插入数据
INSERT INTO geom VALUES (ST_GeomFromText('POINT(1 1)'))
查询内部数据结构
SELECT g FROM geom
查询WKT
SELECT ST_AsText(g) FROM geom
查询WKB
SELECT ST_AsBinary FROM geom
此外常用的还有ST_X,ST_Y,有需要可以去从手册上查
空间索引
mysql的空间索引的数据结构是R树,R树实际上就是多维的B树,说几点在应用的时候需要注意的。
- 建立空间索引需要对应列NOT NULL且有具体的SRID,没有SRID属性的列称为非SRID约束,会接收任何SRID坐标系的值,但是优化器不能再这样没有SRID的列上使用空间索引
- 空间索引只能建立在空间数据类型上
- 如果在不支持空间索引的存储引擎中对非空间列建立索引,则会建立B树索引,可以用于精确查找空间位置,但是不能范围查找(把空间数据列当成字符串去建立索引)
总结
我以 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 | 支持 | 支持 | 支持 | 低 | 版本升级太困难 |
可能测试操作时有些误差,有熟练使用这些数据库的可以评论交流一下。
数据库没有哪个一定好,只要适合场景即可。
参考:
https://www.cnblogs.com/zhenbianshu/p/6817569.html
https://www.jianshu.com/p/74a554ff973a
空间索引 - Redis/MongoDB/PostgreSQL/MySQL空间索引使用报告相关推荐
- 数据库—MySql—Redis—MongoDB—Http协同流程
目录 MySql Mysql中char和varchar有什么区别? Mysql中float和double的区别是什么? Mysql中date和datetime类型的区别? Mysql中sql语句执⾏的 ...
- mysql 空间索引_Mysql空间索引
Mysql 空间索引 本文主要根据mysql 8.0的文档翻译总结,如果使用的是mysql 5.7版本,可能会有些许差异 在涉及LBS的服务开发过程中,经常需要存储地理空间的位置并进行一定计算(附近商 ...
- mysql mongodb 秒杀_绝赞!B站投币20W+的Redis/MongoDB/Mysql性能优化宝藏库,我先磕了
性能优化算是老生常谈的话题了,不管项目大小,一旦上线,或多或少都会遇到性能问题.有些性能问题是随着时间的积累慢慢产生的,比如系统刚上线,数据量很小的时候,没啥问题,等到数据积累到一定程度,问题就暴露出 ...
- monit mysql_monit配置监控启动nginx php mysql redis mongodb 服务器
-----nginx php mysql redis mongodb 配置 完美OK------– # nginx check process nginx withpidfile /usr/local ...
- 搞机:使用docker-compose10分钟搞定redis,mongodb,mysql三大数据库
redis,mongodb,mysql三大数据库应该算是后端最为常用的三种数据库了,部署后端服务的时候一个一个的安装过于麻烦,这里我们结合docker的使用实现快速安装,并使用docker-compo ...
- 【大讲堂讲师专访】张甦:数据库选型需因地制宜,MongoDB与MySQL绝非替代
云和恩墨大讲堂每周都会邀请业内外大咖进行一小时的线上主题分享,本期我们邀请到了恩墨学院张甦老师,带来题为<MongoDB经典案例分析>的分享,敬请期待. 在开课前,大讲堂有幸采访了张老师, ...
- windows 版本 redis mongodb emqttd安装
EMQ 安装与部署 @a.宏万 最近接触到了消息队列,发现了一款开源的MQTT服务器,EMQ. 百万级分布式开源物联网MQTT消息服务器 基于高并发的Erlang/OTP语言平台设计,支持百万级连接和 ...
- 【译】MongoDb vs Mysql—以NodeJs为例
[译]MongoDb vs Mysql-以NodeJs为例 亲爱的读者,您可能想知道为什么要写关于MongoDb和MySql这篇文章.那是因为我与NodeJs开发人员讨论在应用程序中使用哪种数据存储作 ...
- 第五十四期:MongoDB与MySQL:如何选择
MongoDB和MySQL分别是领先的开源NoSQL和关系数据库.哪个最适合您的应用程序? 作者:XEyes行走的CODE来源 MongoDB和MySQL分别是领先的开源NoSQL和关系数据库.哪个最 ...
- redis 和 数据库mysql之间的关系
https://www.zhihu.com/question/20734566 https://www.zhihu.com/question/19660689 http://blog.csdn.net ...
最新文章
- 网站真分页js代码该怎么写?
- bzoj3295:[CQOI2011]动态逆序对
- 最简单的EasyUI菜单栏
- 《全景探秘游戏设计艺术》精华 用户体验 游戏平衡性
- python 装饰器
- Qt 判断文件或文件夹是否存在及创建文件夹
- PyQt5学习--基本窗口控件--QMainWindow
- ie 无人操作自动关闭_Win10系统下ie浏览器无响应白屏自动关闭如何修复
- VBScript 基础知识
- C# 生成word文件 小学一年级口算题生成器(代码)
- 新一代同步控制器和触摸屏组合在压延机上的应用
- mysql 2008降序排列_sql 升序降序排列
- 仿照微信写的uni-app项目
- 原生table 边框重叠 粗细不一样等问题,秒懂,只需几秒就能完成
- 使用excel打开文件,修正长度过长的数字后几位变为0的问题
- webRTC(六):webrtc信令服务器实现
- 【国产HMI引擎】一篇文章带你了解ssRender引擎
- 个人简历——各类奖学金、各种称号、各种职位中英文对照:
- 前端HTTP、浏览器面试题
- Python优缺点总结
热门文章
- 状态机编程 (一) 状态机相关概念
- XSS跨站脚本攻击和预防措施
- 关于四元数与欧拉角协方差阵转换的一些思考
- 用JMS578转接板开卡PS3111主控pSLC模式,带固件下载
- 职业学校计算机课评课,信息技术课评课范文
- PCB Layout总结
- PDF虚拟打印机(pdfFactory) v5.11 官方版
- 利用SolidWorks解决方案取得健身器材市场的领先地位!
- 企业入职测试题目及答案_[企业入职性格测试题以与答案信息]新员工入职性格测试题...
- 解决ES只能查询10000条数据的问题