Postgres/PostGIS 上的 Gist 索引仍然很慢
Gist index on Postgres/PostGIS still slow
我不是 Postgres/GIS 学科的专家,我对大型几何数据库(超过 2000 万条记录)有疑问。首先,我的设置如下所示:
mmt=# select version();
-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------
version | PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
mmt=# select PostGIS_Version();
-[ RECORD 1 ]---+--------------------------------------
postgis_version | 3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
我查询的table包含以下列:
mmt=# \d titles
Table "public.titles"
Column | Type | Collation | Nullable | Default
----------------------+--------------------------+-----------+----------+-----------------------------------------
ogc_fid | integer | | not null | nextval('titles_ogc_fid_seq'::regclass)
wkb_geometry | bytea | | |
timestamp | timestamp with time zone | | |
end | timestamp with time zone | | |
gml_id | character varying | | |
validfrom | character varying | | |
beginlifespanversion | character varying | | |
geom_bounding_box | geometry(Geometry,4326) | | |
Indexes:
"titles_pkey" PRIMARY KEY, btree (ogc_fid)
"geom_idx" gist (geom_bounding_box)
geom_bounding_box 列包含 wkb_geometry 的边界框。我已经创建了那个边界框列,因为 wkb 几何图形超过了 GIST 索引中项目的默认大小限制。其中一些是非常复杂的几何形状,由几十个点组成一个多边形。使用边界框代替意味着我能够在该列上放置索引作为加速搜索的一种方式。至少理论上是这样。
我的搜索旨在找到落在给定点 100 米范围内的几何图形,如下所示,但这需要两分钟多的时间才能 return。我想在一秒钟内完成!:
select ogc_fid, web_geometry from titles where ST_DWithin(geom_bounding_box, 'SRID=4326;POINT(-0.145872 51.509691)'::geography, 100);
下面是一个基本的解释输出。我可以做些什么来加快这件事?
谢谢!
mmt=# explain select ogc_fid from titles where ST_DWithin(geom_bounding_box, 'SRID=4326;POINT(-0.145872 51.509691)'::geography, 100);
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Gather (cost=1000.00..243806855.33 rows=2307 width=4)
-[ RECORD 2 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Workers Planned: 2
-[ RECORD 3 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | -> Parallel Seq Scan on titles (cost=0.00..243805624.63 rows=961 width=4)
-[ RECORD 4 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Filter: st_dwithin((geom_bounding_box)::geography, '0101000020E61000006878B306EFABC2BF6308008E3DC14940'::geography, '100'::double precision, true)
-[ RECORD 5 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | JIT:
-[ RECORD 6 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Functions: 4
-[ RECORD 7 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Options: Inlining true, Optimization true, Expressions true, Deforming true
问题是您混合了 geometry
和 geography
,并且 PostgreSQL 将 geom_bounding_box
转换为 geography
以便它们匹配。
现在您已索引 geom_bounding_box
,但未索引 geom_bounding_box::geography
,这是不同的。
要么使用 'SRID=4326;POINT(-0.145872 51.509691)'::geometry
作为第二个操作数,要么在 ((geom_bounding_box::geography))
上创建 GiST 索引(注意双括号)。
编辑:
正如 mlinth 所指出的,我在下面的回答并不是真的有效。但它会带来一个危险:当心给 ST_DWithin 函数的参数,因为距离参数的单位是不同的,这取决于你给出的是地理(米)还是几何(srid 单位)。
根据ST_DWithin doc,距离以SRID单位指定。在你的例子中,空间参考系统是一个地理参考系统,所以你的 100 值意味着 100 度 半径,不是100米。这意味着大约 整个世界 。在这种情况下,有效地使用索引是不可能的。
如果要查找 100 米半径内的几何图形,必须将 100 米转换为度单位,但这取决于纬度(如果您想要准确的话)。
首先,我建议您使用一个(非常)近似的捷径:赤道 100 米(非常)大约等于 0.001 度。所以用它替换你的距离值,如果它加快了速度(我非常相信它会),那么你将能够改进你的查询以更准确。
我确实解决了这个问题,它是上述所有事情的组合,尽管不是单独的任何一个。快速总结:
Laurenz Albe 正确地发现了地理和几何类型的混合,通过删除转换很容易解决这个问题。
Ian Turton 也正确地发现几十个点不应该成为要点索引的问题,所以我放弃了边界框近似方法并回过头来探索索引问题。我发现几何列是用 'byte array' (bytea) 的数据类型定义的,这阻止了由于 'no default operator class for access method "spgist"' 而创建 spgist 索引这是通过如下更改列类型解决的:
mmt=# ALTER TABLE titles
ALTER COLUMN wkb_geometry
TYPE geometry
USING wkb_geometry::geometry;
然后索引成功创建(gist 或 spgist),我已经能够并排对两者进行基准测试,发现 gist 在我的用例中效率稍微高一些。
Amanin 根据空间参考系统指出米和径向度之间的差异也是正确的。在我的一些测试中,我错误地使用了后者,但在非常大的半径上。由于我正在使用几何类型进行索引和搜索,因此该半径值在径向度数上需要非常小才能覆盖相当大的区域。已修复!
总之,对 2600 万条记录的搜索始终在 200 毫秒到 500 毫秒内完成,偶尔会出现高达 1.1 秒的峰值。这个不错。
感谢所有贡献意见、想法和讨论的人。
我不是 Postgres/GIS 学科的专家,我对大型几何数据库(超过 2000 万条记录)有疑问。首先,我的设置如下所示:
mmt=# select version();
-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------
version | PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
mmt=# select PostGIS_Version();
-[ RECORD 1 ]---+--------------------------------------
postgis_version | 3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
我查询的table包含以下列:
mmt=# \d titles
Table "public.titles"
Column | Type | Collation | Nullable | Default
----------------------+--------------------------+-----------+----------+-----------------------------------------
ogc_fid | integer | | not null | nextval('titles_ogc_fid_seq'::regclass)
wkb_geometry | bytea | | |
timestamp | timestamp with time zone | | |
end | timestamp with time zone | | |
gml_id | character varying | | |
validfrom | character varying | | |
beginlifespanversion | character varying | | |
geom_bounding_box | geometry(Geometry,4326) | | |
Indexes:
"titles_pkey" PRIMARY KEY, btree (ogc_fid)
"geom_idx" gist (geom_bounding_box)
geom_bounding_box 列包含 wkb_geometry 的边界框。我已经创建了那个边界框列,因为 wkb 几何图形超过了 GIST 索引中项目的默认大小限制。其中一些是非常复杂的几何形状,由几十个点组成一个多边形。使用边界框代替意味着我能够在该列上放置索引作为加速搜索的一种方式。至少理论上是这样。
我的搜索旨在找到落在给定点 100 米范围内的几何图形,如下所示,但这需要两分钟多的时间才能 return。我想在一秒钟内完成!:
select ogc_fid, web_geometry from titles where ST_DWithin(geom_bounding_box, 'SRID=4326;POINT(-0.145872 51.509691)'::geography, 100);
下面是一个基本的解释输出。我可以做些什么来加快这件事?
谢谢!
mmt=# explain select ogc_fid from titles where ST_DWithin(geom_bounding_box, 'SRID=4326;POINT(-0.145872 51.509691)'::geography, 100);
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Gather (cost=1000.00..243806855.33 rows=2307 width=4)
-[ RECORD 2 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Workers Planned: 2
-[ RECORD 3 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | -> Parallel Seq Scan on titles (cost=0.00..243805624.63 rows=961 width=4)
-[ RECORD 4 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Filter: st_dwithin((geom_bounding_box)::geography, '0101000020E61000006878B306EFABC2BF6308008E3DC14940'::geography, '100'::double precision, true)
-[ RECORD 5 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | JIT:
-[ RECORD 6 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Functions: 4
-[ RECORD 7 ]----------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN | Options: Inlining true, Optimization true, Expressions true, Deforming true
问题是您混合了 geometry
和 geography
,并且 PostgreSQL 将 geom_bounding_box
转换为 geography
以便它们匹配。
现在您已索引 geom_bounding_box
,但未索引 geom_bounding_box::geography
,这是不同的。
要么使用 'SRID=4326;POINT(-0.145872 51.509691)'::geometry
作为第二个操作数,要么在 ((geom_bounding_box::geography))
上创建 GiST 索引(注意双括号)。
编辑: 正如 mlinth 所指出的,我在下面的回答并不是真的有效。但它会带来一个危险:当心给 ST_DWithin 函数的参数,因为距离参数的单位是不同的,这取决于你给出的是地理(米)还是几何(srid 单位)。
根据ST_DWithin doc,距离以SRID单位指定。在你的例子中,空间参考系统是一个地理参考系统,所以你的 100 值意味着 100 度 半径,不是100米。这意味着大约 整个世界 。在这种情况下,有效地使用索引是不可能的。
如果要查找 100 米半径内的几何图形,必须将 100 米转换为度单位,但这取决于纬度(如果您想要准确的话)。
首先,我建议您使用一个(非常)近似的捷径:赤道 100 米(非常)大约等于 0.001 度。所以用它替换你的距离值,如果它加快了速度(我非常相信它会),那么你将能够改进你的查询以更准确。
我确实解决了这个问题,它是上述所有事情的组合,尽管不是单独的任何一个。快速总结:
Laurenz Albe 正确地发现了地理和几何类型的混合,通过删除转换很容易解决这个问题。
Ian Turton 也正确地发现几十个点不应该成为要点索引的问题,所以我放弃了边界框近似方法并回过头来探索索引问题。我发现几何列是用 'byte array' (bytea) 的数据类型定义的,这阻止了由于 'no default operator class for access method "spgist"' 而创建 spgist 索引这是通过如下更改列类型解决的:
mmt=# ALTER TABLE titles
ALTER COLUMN wkb_geometry
TYPE geometry
USING wkb_geometry::geometry;
然后索引成功创建(gist 或 spgist),我已经能够并排对两者进行基准测试,发现 gist 在我的用例中效率稍微高一些。
Amanin 根据空间参考系统指出米和径向度之间的差异也是正确的。在我的一些测试中,我错误地使用了后者,但在非常大的半径上。由于我正在使用几何类型进行索引和搜索,因此该半径值在径向度数上需要非常小才能覆盖相当大的区域。已修复!
总之,对 2600 万条记录的搜索始终在 200 毫秒到 500 毫秒内完成,偶尔会出现高达 1.1 秒的峰值。这个不错。
感谢所有贡献意见、想法和讨论的人。