首页 > 解决方案 > 对大型空间选择进行排序不使用 GiST 索引(Postgres 11.5)

问题描述

我有一个表 ( demo),其中一个序列作为其主键 ( seqno) 和一个geometry包含在 JSONB 列 ( doc) 中的属性。我为序列列配置了一个主键约束,为几何配置了一个 GiST 索引。我已经通过运行收集了统计数据VACUUM ANALYZE。这是一个相当大的表(42M 行)。

CREATE TABLE demo
(
    seqno bigint NOT NULL DEFAULT nextval('seqno'::regclass),
    doc jsonb NOT NULL DEFAULT '{}'::jsonb,
    CONSTRAINT demo_pkey PRIMARY KEY (seqno)
)

CREATE INDEX demo_doc_geometry_gist
ON demo USING gist (st_geometryfromtext(doc ->> 'geometry'::text))

我想在相当大的区域上执行空间过滤器并返回前 10 行,按其主键排序。因此,我尝试了以下查询:

SELECT seqno, doc
FROM demo
WHERE ST_Within(ST_GeometryFromText((doc->>'geometry')), ST_GeometryFromText('POLYGON((4.478054829251019 52.61266886732067,5.247097798001019 52.61266886732067,5.247097798001019 52.156694555984416,4.478054829251019 52.156694555984416,4.478054829251019 52.61266886732067))'))
ORDER BY seqno
LIMIT 10

这会产生以下查询计划:

Limit  (cost=1000.59..15169.06 rows=10 width=633) (actual time=2479.372..2496.737 rows=10 loops=1)
  ->  Gather Merge  (cost=1000.59..19780184.81 rows=13960 width=633) (actual time=2479.370..2496.732 rows=10 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        ->  Parallel Index Scan using demo_pkey on demo  (cost=0.56..19777573.45 rows=5817 width=633) (actual time=2440.310..2450.101 rows=5 loops=3)
              Filter: (('0103000020407100000100000005000000CFCA3EB32997F4402D3225A6F0D02041DDFD612B4A5F0141D66C69E40CCD20415E0E6F193D580141AE7BECF122511C412C99A20E8F48F440E6B3764403591C41CFCA3EB32997F4402D3225A6F0D02041'::geometry ~ st_geometryfromtext((doc ->> 'geometry'::text))) AND _st_contains('0103000020407100000100000005000000CFCA3EB32997F4402D3225A6F0D02041DDFD612B4A5F0141D66C69E40CCD20415E0E6F193D580141AE7BECF122511C412C99A20E8F48F440E6B3764403591C41CFCA3EB32997F4402D3225A6F0D02041'::geometry, st_geometryfromtext((doc ->> 'geometry'::text))))
              Rows Removed by Filter: 221313
Planning Time: 0.375 ms
Execution Time: 2496.786 ms

这说明使用主键约束索引扫描所有行并对每一行进行空间过滤,这显然是非常低效的。给定的空间谓词有超过 5M 的匹配。根本不使用 GiST 索引。

但是,当省略 ORDER BY 子句时,几何属性的 GiST 索引会被正确使用,这样效率更高。

Limit  (cost=0.42..128.90 rows=10 width=633) (actual time=0.381..0.745 rows=10 loops=1)
  ->  Index Scan using demo_doc_geometry_gist on demo  (cost=0.42..179352.99 rows=13960 width=633) (actual time=0.380..0.742 rows=10 loops=1)
        Index Cond: ('0103000020407100000100000005000000CFCA3EB32997F4402D3225A6F0D02041DDFD612B4A5F0141D66C69E40CCD20415E0E6F193D580141AE7BECF122511C412C99A20E8F48F440E6B3764403591C41CFCA3EB32997F4402D3225A6F0D02041'::geometry ~ st_geometryfromtext((doc ->> 'geometry'::text)))
        Filter: _st_contains('0103000020407100000100000005000000CFCA3EB32997F4402D3225A6F0D02041DDFD612B4A5F0141D66C69E40CCD20415E0E6F193D580141AE7BECF122511C412C99A20E8F48F440E6B3764403591C41CFCA3EB32997F4402D3225A6F0D02041'::geometry, st_geometryfromtext((doc ->> 'geometry'::text)))
Planning Time: 0.245 ms
Execution Time: 0.780 ms

有没有办法让这个查询快速?我们可以让查询规划器将 GiST 索引与 PK 索引结合起来得到一个排序的结果吗?还有其他建议吗?

标签: postgresqlpostgissql-execution-planpostgresql-11query-planner

解决方案


这说明使用主键约束索引扫描所有行

它不会扫描所有行,它会在找到其中匹配的 10 个后停止。这似乎是大约 221313 * 3 + 10 行,或大约总行数的 1.6%。不明显这是错误的做法。您可以通过更改为来禁止使用主键索引ORDER BY seqno+0。这应该使用 GiST 索引,但我不会指望这会更快。

但是,当省略 ORDER BY 子句时,几何属性的 GiST 索引会被正确使用,这样效率更高。

但它回答了一个简单得多的问题。考虑一下“给我找 5 个来自芝加哥的随机人”和“给我找 5 个芝加哥最高的人”之间的区别。

至于使查询更快,我会尝试这个ORDER BY seqno+0技巧。我不认为它会更快,但我可能是错的。

我还会尝试使用 btree 索引,(seqno, doc)这样您就可以获得仅索引扫描,尽管如果您的几何图形位于其自己的列中而不嵌入 JSONB 中,这会更好,因此您可以仅索引 seqno 和几何图形而不是比整个 JSONB。理论上,PostgreSQL 可以为您提供一个仅扫描索引的索引(seqno, ST_GeometryFromText(doc->>'geometry')),但它还不够聪明,无法实现这一点。

(seqno, ST_GeometryFromText(doc->>'geometry'))您还可以尝试使用 btree_gist 扩展来启用包含 seqno的多列 GiST 索引。

最后,您可以尝试在 seqno 上对表进行范围分区。这将需要重新组织您的数据集,因此并不像构建索引那么简单。


推荐阅读