当前的 PostgreSQL 版本(包括 9.6)在处理 TOAST 表时存在查询优化器方面的弱点。TOAST 表是一种“扩展空间”,用于存储不适合正常数据页面的大型值(在数据大小方面),例如长文本、图像或具有大量顶点的高度复杂的几何图形,有关更多信息,请参阅 PostgreSQL 文档中关于 TOAST 的说明。
如果您有一个包含相当大的几何图形但行数不多的表(例如,包含所有欧洲国家/地区高分辨率边界的表),则会出现此问题。然后,该表本身很小,但它使用了大量的 TOAST 空间。在我们的示例中,该表本身大约有 80 行,仅使用了 3 个数据页,但 TOAST 表使用了 8225 个页面。
现在发出一个查询,其中您使用几何运算符 && 来搜索仅匹配极少行的边界框。现在,查询优化器看到该表只有 3 页和 80 行。它估计对此类小表进行顺序扫描比使用索引快得多。因此,它决定忽略 GIST 索引。通常,这种估计是正确的。但在我们的例子中,&& 运算符必须从磁盘获取每个几何图形以比较边界框,因此也读取所有 TOAST 页面。
要查看您是否遇到了此问题,请使用“EXPLAIN ANALYZE”postgresql 命令。有关更多信息和技术细节,您可以阅读 PostgreSQL 性能邮件列表上的线程:http://archives.postgresql.org/pgsql-performance/2005-02/msg00030.php
以及 PostGIS 上较新的线程 https://lists.osgeo.org/pipermail/postgis-devel/2017-June/026209.html
PostgreSQL 的开发人员正在尝试通过使查询估计感知 TOAST 来解决此问题。目前,这里有两种解决方法
第一种解决方法是强制查询规划器使用索引。在发出查询之前,向服务器发送 “SET enable_seqscan TO off;”。这基本上强制查询规划器尽可能避免顺序扫描。因此,它像往常一样使用 GIST 索引。但是此标志必须在每个连接上设置,并且会导致查询规划器在其他情况下产生错误估计,因此您应该在查询后“SET enable_seqscan TO on;”。
第二种解决方法是使顺序扫描像查询规划器认为的那样快。这可以通过创建一个“缓存”边界框的附加列,并将其与此列匹配来实现。在我们的示例中,命令如下所示
SELECT AddGeometryColumn('myschema','mytable','bbox','4326','GEOMETRY','2'); UPDATE mytable SET bbox = ST_Envelope(ST_Force2D(geom));
现在,更改您的查询以使用针对 bbox 而不是 geom_column 的 && 运算符,如下所示
SELECT geom_column FROM mytable WHERE bbox && ST_SetSRID('BOX3D(0 0,1 1)'::box3d,4326);
当然,如果更改或向 mytable 添加行,则必须保持 bbox “同步”。最透明的方法是使用触发器,但您也可以修改应用程序以保持 bbox 列的最新状态,或者在每次修改后运行上述 UPDATE 查询。
对于主要为只读的表,并且其中单个索引用于大多数查询,PostgreSQL 提供了 CLUSTER 命令。此命令以与索引条件相同的顺序物理地重新排序所有数据行,从而产生两个性能优势:首先,对于索引范围扫描,数据表上的寻道次数大大减少。其次,如果您的工作集集中在索引上的一些小间隔上,则由于数据行分布在较少的数据页面上,因此您将拥有更高效的缓存。(请阅读 PostgreSQL 手册中关于 CLUSTER 命令的文档。)
但是,当前 PostgreSQL 不允许在 PostGIS GIST 索引上进行聚类,因为 GIST 索引只是简单地忽略 NULL 值,您会收到如下错误消息
lwgeom=# CLUSTER my_geom_index ON my_table; ERROR: cannot cluster when index access method does not handle null values HINT: You may be able to work around this by marking column "geom" NOT NULL.
正如 HINT 消息告诉您的那样,可以通过向表中添加“not null”约束来解决此缺陷
lwgeom=# ALTER TABLE my_table ALTER COLUMN geom SET not null; ALTER TABLE
当然,如果您实际上需要在几何列中使用 NULL 值,则此方法将不起作用。此外,您必须使用上述方法来添加约束,使用类似 “ALTER TABLE blubb ADD CHECK (geometry is not null);” 的 CHECK 约束将不起作用。
有时,您碰巧在表中拥有 3D 或 4D 数据,但始终使用仅输出 2D 几何图形的符合 OpenGIS 的 ST_AsText() 或 ST_AsBinary() 函数访问它。它们通过内部调用 ST_Force2D() 函数来实现此目的,这会为大型几何图形引入显著的开销。为避免这种开销,预先一次性删除这些额外的维度可能是可行的
UPDATE mytable SET geom = ST_Force2D(geom); VACUUM FULL ANALYZE mytable;
请注意,如果您使用 AddGeometryColumn() 添加了几何列,则会对几何维度进行约束。要绕过它,您将需要删除该约束。请记住更新 geometry_columns 表中的条目,并在之后重新创建约束。
对于大型表,通过 WHERE 子句和主键或其他可行的条件将 UPDATE 限制为表的一部分,并将 UPDATE 分成较小的部分可能是明智的,并在 UPDATE 之间运行简单的“VACUUM;”。这大大减少了对临时磁盘空间的需求。此外,如果您具有混合维度的几何图形,则通过“WHERE dimension(geom)>2”限制 UPDATE 会跳过对已为 2D 的几何图形的重写。