Chapter 6. 性能优化技巧

Table of Contents

6.1. 大几何形状的小表

6.1.1. 问题描述

当前版本的PostgreSQL(包括9.6)在TOAST表后面的查询优化器中存在弱点。TOAST 表是一种“扩展室”,用于存储不适合普通数据页面(例如长文本、图像或具有许多顶点的复杂几何图形)的巨大值(就数据大小而言)。 有关更多信息,请参阅 Toast的PostgreSQL文档

如果您碰巧有一个具有相当大的几何形状的表格,但它们的行数不是太多(例如包含所有欧洲国家的高分辨率边界的表格),则会出现问题。 那么表本身很小,但是它使用了大量的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;” 。

第二个解决方法是使顺序扫描与查询规划器认为的一样快。这可以通过创建一个“缓存”bbox的附加列并与之匹配来实现。在我们的示例中,命令如下:

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. 几何索引聚类

对于大多数只读的表,以及大多数查询使用单个索引的情况,PostgreSQL 提供了 CLUSTER 命令。该命令按照与索引标准相同的顺序对所有数据行进行物理重新排序,从而产生两个性能优势:首先,对于索引范围扫描,数据表上的查找次数大大减少。其次,如果您的工作集集中在索引上的一些小间隔,则您的缓存会更有效,因为数据行分布在更少的数据页上。(此时请阅读 PostgreSQL 手册中的 CLUSTER 命令文档。)

但是,由于 GiST 索引只是忽略 NULL 值,因此当前无法在 PostGIS 中对 GiST 索引进行聚类,并且您会收到以下错误消息:

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.

正如提示消息所说,向表添加“非空”限制将暂时解决此缺陷。 例如:

lwgeom=# ALTER TABLE my_table ALTER COLUMN geom SET not null;
ALTER TABLE

当然,如果您实际上需要几何列中的 NULL 值,则这将不起作用。 此外,您必须使用上述方法添加约束,使用 CHECK 约束,例如“ALTER TABLE blubb ADD CHECK (geometry is not null);” 不管用。

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 限制为表的一部分,然后运行简单的“VACUUM;”,从而将此 UPDATE 划分为更小的部分。 在您的更新之间。 这大大减少了对临时磁盘空间的需求。此外,如果您有混合维度几何图形,则通过“WHEREdimension(geom)>2”限制 UPDATE 会跳过对已经处于 2D 中的几何图形的重写。