第 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 資料,但始終使用符合 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 的幾何物件。