我有一个表(demo
(,它的主键是序列(seqno
(,JSONB列(doc
(中包含一个geometry
属性。我已经为序列列配置了主键约束,并为几何体配置了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索引结合起来得到排序结果吗?还有其他建议吗?
这表明主键约束索引用于扫描的所有行
它不会扫描所有行,而是在找到10个匹配的行后停止。这看起来大约是221313*3+10行,或者大约是总行的1.6%。这显然是错误的做法。您可以通过更改为ORDER BY seqno+0
来抑制主键索引的使用。这应该使用GiST索引,但我不认为这会更快。
但是,在省略ORDER BY子句时,可以正确使用几何体属性的GiST索引,这要高效得多。
但它回答了一个简单得多的问题。考虑";从芝加哥随机找5个人";以及";给我找芝加哥最高的五个人";。
至于让查询更快,我会尝试ORDER BY seqno+0
技巧。我不认为它会更快,但我可能错了。
我也会在(seqno, doc)
上尝试btree索引,这样你就可以获得只索引的扫描,尽管如果你的几何体在它自己的列中,而不是嵌入在JSONB中,这会更好,所以你可以只索引seqno和几何体,而不是整个JSONB。理论上,PostgreSQL可以为您提供只扫描(seqno, ST_GeometryFromText(doc->>'geometry'))
上的索引的索引,但它还不够聪明,无法实现这一点。
您也可以尝试在(seqno, ST_GeometryFromText(doc->>'geometry'))
上使用btree_GiST扩展名创建多列GiST索引,以允许包含seqno。
最后,您可以尝试在seqno上对表进行范围分区。这需要对数据集进行重组,所以这并不像构建索引那么简单。
您可以尝试在查询中包括边界框重叠运算符~
,正如文档所说的
此操作数将使用几何图形。
SELECT seqno, doc
FROM demo
WHERE 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))')
AND 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
子句且偏移量为0的情况下运行查询,以防止内联子查询,然后应用限制。
SELECT * FROM (
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))')
OFFSET 0
) sub
ORDER BY seqno
LIMIT 10