当前的 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 数据,但始终使用符合 OpenGIS 标准的 ST_AsText() 或 ST_AsBinary() 函数访问这些数据,这些函数只输出 2D 几何图形。它们通过内部调用 ST_Force2D() 函数来实现这一点,这会给大型几何图形带来显著的开销。为了避免这种开销,可以考虑永久地预先删除这些额外的维度。
UPDATE mytable SET geom = ST_Force2D(geom); VACUUM FULL ANALYZE mytable;
请注意,如果您使用 AddGeometryColumn() 添加了几何图形列,则会对几何图形维度施加约束。要绕过它,您需要删除约束。请记住,在之后更新 geometry_columns 表中的条目并重新创建约束。
对于大型表,明智的做法是将此 UPDATE 分成更小的部分,通过 WHERE 子句和您的主键或其他可行的条件将 UPDATE 限制在表的某一部分,并在 UPDATE 之间运行简单的 "VACUUM;"。这极大地减少了对临时磁盘空间的需求。此外,如果您有混合维度的几何图形,通过 "WHERE dimension(geom)>2" 限制 UPDATE 可以跳过对已经处于 2D 的几何图形的重写。