第 6 章。性能提示

目录

6.1. 包含大型几何图形的小表

6.1.1. 问题描述

当前的 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

6.1.2. 解决方法

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 查询。

6.2. 在几何索引上进行 CLUSTER 操作

对于主要为只读的表,并且其中单个索引用于大多数查询,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 约束将不起作用。

6.3. 避免维度转换

有时,您碰巧在表中拥有 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 的几何图形的重写。