为什么geometry+GIST 比 geohash+BTREE更适合空间搜索 - 多出的不仅仅是20倍性能提升...

本文主要是介绍为什么geometry+GIST 比 geohash+BTREE更适合空间搜索 - 多出的不仅仅是20倍性能提升...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

标签

PostgreSQL , gist , btree , 空间索引 , 范围扫描


背景

在PostgreSQL中,支持geohash, geometry, geograph三种空间存储结构。

1、geohash,很多库都支持它,因为简单,将地球作为标准化的球体,展开抽象为一个平面,划分为若干个小方格,进行编码,相邻的小方格的编码前缀一样。

pic

pic

geohash 每一个小方块的精度与编码长度有关(这个说法也不完全准确,因为是基于地球是标准球体的前提),如下:

pic

2、由于地球并非标准球体,也非标准的椭球体,所以geohash精度有硬性的缺陷,geometry与geograph类型,可以解决这个问题。

pic

对于GIS来说,首先是坐标系,有两种:一种是球坐标(地理坐标),另一种是平面坐标(投影坐标)。

球坐标通常用于计算,平面坐标通常用于展示(也可以计算)。

投影坐标是从球坐标投影后展开得来(用一个圆柱将地球包起来,把地球当成会发光的光源,投影后,将圆柱展开得到),投影的范围越大,精度就越低。范围越小,

计算距离,应该考虑到被计算的两点所在处的地球特性(spheroid)。这样计算得到的距离才是最精确的。

geometry和geography类型的选择,建议使用geometry,既能支持球坐标系,又能支持平面坐标系。主要考虑到用户是否了解位置所在处的地理特性,选择合适的坐标系。

目前用得最多的有SRID=4326球坐标,SRID为EPSG:3857的墨卡托投影坐标。

再来说geometry和geography两种类型,geometry支持平面对象也支持空间对象,而geography则仅支持空间对象。

geometry支持更多的函数,一些几何计算的代价更低。

geography支持的函数略少,计算代价更高。但是对于跨度较大地域性的业务,就需要使用geography,因为它的精度不受制于区域。

If your data is contained in a small area, you might find that choosing an appropriate
projection and using GEOMETRY is the best solution, in terms of performance and functionality available.

If your data is global or covers a continental region, you may find that GEOGRAPHY
allows you to build a system without having to worry about projection details.
You store your data in longitude/latitude, and use the functions that have been defined on GEOGRAPHY.

If you don't understand projections, and you don't want to learn about them,
and you're prepared to accept the limitations in functionality available in GEOGRAPHY,
then it might be easier for you to use GEOGRAPHY than GEOMETRY.
Simply load your data up as longitude/latitude and go from there.

除了空间模型上的差异,geohash与geometry, geograph还有功能、性能上的差异。

性能方面主要体现在GEOHASH的编码精度会带来一些问题:

1、由于GEOHASH编码的问题,我们在搜索某一个点附近N米内的对象时,会引入空间放大,理论上我们要的是以目标点为中心,距离为半径的一个圆内的数据。

pic

如果只看前缀的话,这个放大会随着编码长度缩短而级数增加。

pic

然而,使用geometry的距离搜索,不会引入放大问题,使用GIST索引按距离排序输出加上st_dwithin约束,返回的一定是在圆圈内的数据,并且不造成额外的RECHECK FILTER。

pic

又比如在GIS北京峰会上探探的一个案例,搜索附近的10家餐馆,在POI密集的地方,一个小的BOX可就圈出几千家餐馆了,而在偏远地区,你就需要一个较大的BOX,还不一定能圈到10家餐馆。

pic

2、当我们需要搜索的是任意多边形时,GEOHASH也无法满足需求,需要进行大范围的匹配,然后再逐条进行空间计算过滤。

几种地理数据的扫描方法

1、geohash 前缀扫描,匹配在这个正方形块内的数据

postgres=# create table t_test(  id int,   pos text,   -- geohash  geo geometry  -- geometry  
);  
CREATE TABLE  postgres=# insert into t_test   
select id,   
st_geohash(st_setsrid(st_point(x,y),4326), 13),   
st_setsrid(st_point(x,y),4326)   
from (  select id, 120+30*random() x, 68+5*random() y   from generate_series(1,100000) t(id)   
) t;  
INSERT 0 100000  
postgres=# select * from t_test limit 10;  id |      pos      |                        geo                           
----+---------------+----------------------------------------------------  1 | yu0j8y2pxsezp | 0101000020E61000000000625C21F25E400000510228205140  2 | zhsfe7t2cbtzz | 0101000020E6100000008049BE8DBA61400080CB2C5DB15140  3 | zhcydqptr7bkd | 0101000020E6100000000061ED403261400000A01B4B395240  4 | yuhdce4q6u7t6 | 0101000020E610000000808C51B6446040008055F70F005140  5 | yus98nqjtdf4r | 0101000020E610000000803D75C54260400080923722A75140  6 | zk9grxnsqxv98 | 0101000020E61000000000787897A16240008086A312BB5140  7 | yurhhfh33u5xm | 0101000020E61000000080C877DEB96040008031E3B7675140  8 | zhk5qv4vhe10k | 0101000020E610000000002A889E9D61400080CA5360605140  9 | zhm49th6m0h5y | 0101000020E61000000000C79D4DC361400000B456E8575140  10 | zh95n0wvxkpv5 | 0101000020E610000000808F92BE1561400000A9D5FCB55140  
(10 rows)  
postgres=# create index idx_t_test_1 on t_test (pos text_pattern_ops);  
CREATE INDEX  
postgres=# explain select * from t_test where pos ~ '^yuhdce4';  QUERY PLAN                                    
-----------------------------------------------------------------------------  Index Scan using idx_t_test_1 on t_test  (cost=0.42..2.64 rows=10 width=50)  Index Cond: ((pos ~>=~ 'yuhdce4'::text) AND (pos ~<~ 'yuhdce5'::text))  Filter: (pos ~ '^yuhdce4'::text)  
(3 rows)  postgres=# explain select * from t_test where pos like 'yuhdce4%';  QUERY PLAN                                    
-----------------------------------------------------------------------------  Index Scan using idx_t_test_1 on t_test  (cost=0.42..2.64 rows=10 width=50)  Index Cond: ((pos ~>=~ 'yuhdce4'::text) AND (pos ~<~ 'yuhdce5'::text))  Filter: (pos ~~ 'yuhdce4%'::text)  
(3 rows)  

2、geohash 范围扫描,匹配在一个连续Z空间中的一段小方格

pic

将二进制编码的结果填写到空间中,当将空间划分为四块时候,编码的顺序分别是左下角00,左上角01,右下脚10,右上角11,也就是类似于Z的曲线,当我们递归的将各个块分解成更小的子块时,编码的顺序是自相似的(分形),每一个子快也形成Z曲线,这种类型的曲线被称为Peano空间填充曲线。

postgres=# explain select * from t_test where pos ~>=~ 'yuhdce4' and pos ~<=~ 'yuhdcej';  QUERY PLAN                                   
----------------------------------------------------------------------------  Index Scan using idx_t_test_1 on t_test  (cost=0.42..2.64 rows=1 width=50)  Index Cond: ((pos ~>=~ 'yuhdce4'::text) AND (pos ~<=~ 'yuhdcej'::text))  
(2 rows)  

3、经度与维度范围扫描,将经度与维度分开两个字段存储。扫描得到的是一个落在经纬度区间内的长方形区间。

create table t_geo (id int, x float, y float);  insert into t_geo   select id, 120+30*random() x, 68+5*random() y   from generate_series(1,100000) t(id) ;  
postgres=# create index idx_t_geo_1 on t_geo (x,y);  
CREATE INDEX  postgres=# explain select * from t_geo where x >= 120 and x <=124 and y >= 68 and y <=71;  QUERY PLAN                                                                         
-------------------------------------------------------------------------------------------------------------------------------------------------------  Index Scan using idx_t_geo_1 on t_geo  (cost=0.42..1029.31 rows=7810 width=20)  Index Cond: ((x >= '120'::double precision) AND (x <= '124'::double precision) AND (y >= '68'::double precision) AND (y <= '71'::double precision))  
(2 rows)  

4、GEOMETRY GIS空间扫描

gist 是基于gist的r-tree,每一层为一个BOUND,当需要搜索包含、相交、近邻时,可以快速定位到与你输入的对象 包含、相交、近邻 的对象。

详细结构建本文末尾的参考部分。

create index idx_t_test_2 on t_test using gist (geo);  

GIST索引不仅支持空间检索,还支持空间排序。

postgres=# explain select * from t_test where st_dwithin(geo, st_setsrid(st_point(121, 70), 4326), 10000) order by geo <-> st_setsrid(st_point(121, 70), 4326);  QUERY PLAN                             
-------------------------------------------------------------------------------------------------------------------------------------------------  Index Scan using idx_t_test_2 on t_test  (cost=0.28..29263.75 rows=6667 width=58)  Index Cond: (geo && '0103000020E6100000010000000500000000000000804BC3C0000000000065C3C000000000804BC3C00000000000ABC3400000000080C4C3400000000000ABC3400000000080C4C340000000000065C3C000000000804BC3C0000000000065C3C0'::geometry)  Order By: (geo <-> '0101000020E61000000000000000405E400000000000805140'::geometry)  Filter: (('0101000020E61000000000000000405E400000000000805140'::geometry && st_expand(geo, '10000'::double precision)) AND _st_dwithin(geo, '0101000020E61000000000000000405E400000000000805140'::geometry, '10000'::double precision))  
(4 rows)  

在LBS项目中,按距离由近到远排序输出,是非常强烈的需求。

性能对比

GIST索引比两个字段复合索引要快很多,原因是复合索引在驱动列使用范围时,这个范围下的所有ENTRY都要被扫描。

同样的测试对比如下:

《PostgreSQL 黑科技 range 类型及 gist index 20x+ speedup than Mysql index combine query》

小结

1、GEOHASH,适合对精度没有要求(例如本土化,小范围的业务),并且舍得浪费计算资源的场景(因为颗粒度大,所以通过索引圈出的区域,可能有很多无效数据,需要大量RECHECK),同时GEOHASH不支持排序,所以需要额外的排序开销。

2、geometry,空间索引,适合对精度要求高的场景,且节约资源。适合专业的GIS业务。geometry使用时,需要注意选择正确的坐标系。geograph则对坐标系没有要求。

3、在一个对象稀疏的区域,圈出附近100个点。与在一个对象密集的区域,圈出附近100个点。使用GEOHASH完全不知所措,因为你不知道该用多大的PREFIX合适,而使用geometry+gist,非常容易且高效率的解决这个问题。

select * from tbl where pos ~ '^geohash_多长合适呢? 不知道' limit 100;  
select * from tbl order by geo <-> 点 limit 100;  

在PG里面,我们同时支持geohash, geometry, geograph三种空间存储,你喜欢什么样的姿势,就用什么样样的姿势。这就是我们喜爱的PostgreSQL。

参考

《geohash vs PostGIS》

《PostgreSQL 黑科技 - 空间聚集存储, 内窥GIN, GiST, SP-GiST索引》

《通过空间思想理解GiST索引的构造》

《PostGIS空间索引(GiST、BRIN、R-Tree)选择、优化 - 阿里云RDS PostgreSQL最佳实践》

《自动选择正确索引访问接口(btree,hash,gin,gist,sp-gist,brin,bitmap...)的方法》

《深入浅出PostgreSQL B-Tree索引结构》

《HTAP数据库 PostgreSQL 场景与性能测试之 6 - (OLTP) 空间应用 - KNN查询(搜索附近对象,由近到远排序输出)》

《GIS附近查找性能优化 - PostGIS long lat geometry distance search tuning using gist knn function》

《PostGIS 距离计算建议 - 投影 与 球 坐标系, geometry 与 geography 类型》

http://www.cnblogs.com/LBSer/p/3310455.html

这篇关于为什么geometry+GIST 比 geohash+BTREE更适合空间搜索 - 多出的不仅仅是20倍性能提升...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/606689

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

查看Oracle数据库中UNDO表空间的使用情况(最新推荐)

《查看Oracle数据库中UNDO表空间的使用情况(最新推荐)》Oracle数据库中查看UNDO表空间使用情况的4种方法:DBA_TABLESPACES和DBA_DATA_FILES提供基本信息,V$... 目录1. 通过 DBjavascriptA_TABLESPACES 和 DBA_DATA_FILES

Golang中拼接字符串的6种方式性能对比

《Golang中拼接字符串的6种方式性能对比》golang的string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去,主要有6种拼接方式,下面小编就来为大家详细讲讲吧... 目录拼接方式介绍性能对比测试代码测试结果源码分析golang的string类型是不可修改的,对于拼接字

Python使用DeepSeek进行联网搜索功能详解

《Python使用DeepSeek进行联网搜索功能详解》Python作为一种非常流行的编程语言,结合DeepSeek这一高性能的深度学习工具包,可以方便地处理各种深度学习任务,本文将介绍一下如何使用P... 目录一、环境准备与依赖安装二、DeepSeek简介三、联网搜索与数据集准备四、实践示例:图像分类1.

mysql线上查询之前要性能调优的技巧及示例

《mysql线上查询之前要性能调优的技巧及示例》文章介绍了查询优化的几种方法,包括使用索引、避免不必要的列和行、有效的JOIN策略、子查询和派生表的优化、查询提示和优化器提示等,这些方法可以帮助提高数... 目录避免不必要的列和行使用有效的JOIN策略使用子查询和派生表时要小心使用查询提示和优化器提示其他常

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

Tomcat高效部署与性能优化方式

《Tomcat高效部署与性能优化方式》本文介绍了如何高效部署Tomcat并进行性能优化,以确保Web应用的稳定运行和高效响应,高效部署包括环境准备、安装Tomcat、配置Tomcat、部署应用和启动T... 目录Tomcat高效部署与性能优化一、引言二、Tomcat高效部署三、Tomcat性能优化总结Tom

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.