為什麼我在 Postgres 13 中的空間查詢比在 Postgres 11 中慢?
Postgres 版本
- x86_64-pc-linux-musl 上的 PostgreSQL 13.2,由 gcc (Alpine 10.2.1_pre1) 10.2.1 20201203 編譯,64 位
- x86_64-pc-linux-gnu 上的 PostgreSQL 11.11 (Debian 11.11-1.pgdg90+1),由 gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516,64 位編譯
詢問
SELECT count(*) FROM "addresses" AS a1 WHERE ST_DWithin('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography, a1.geom, '25000');
結果是預期的結果。但在 PG11 上慢(1.1s),在 PG13 上更慢(2.9s)
表定義
Table "public.addresses" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -------------+--------------------------------+-----------+----------+---------------------------------------+----------+--------------+------------- id | bigint | | not null | nextval('addresses_id_seq'::regclass) | plain | | description | text | | | | extended | | country | character varying(255) | | | | extended | | locality | character varying(255) | | | | extended | | region | character varying(255) | | | | extended | | postal_code | character varying(255) | | | | extended | | street | text | | | | extended | | geom | geometry | | | | main | | inserted_at | timestamp(0) without time zone | | not null | | plain | | updated_at | timestamp(0) without time zone | | not null | | plain | | url | character varying(255) | | not null | | extended | | origin_id | character varying(255) | | | | extended | | type | character varying(255) | | | | extended | | Indexes: "addresses_pkey" PRIMARY KEY, btree (id) "addresses_origin_id_index" UNIQUE, btree (origin_id) "addresses_url_index" UNIQUE, btree (url) "idx_addresses_geom" gist (geom) Referenced by: TABLE "actors" CONSTRAINT "actors_physical_address_id_fkey" FOREIGN KEY (physical_address_id) REFERENCES addresses(id) TABLE "events" CONSTRAINT "events_physical_address_id_fkey" FOREIGN KEY (physical_address_id) REFERENCES addresses(id) Access method: heap
基數
SELECT ST_SRID(geom) AS srid, count(*) from addresses group by 1; srid | count ------+-------- | 1 4326 | 265011
查詢計劃
PG11
Finalize Aggregate (cost=52276.51..52276.52 rows=1 width=8) (actual time=1082.209..1095.487 rows=1 loops=1) Buffers: shared hit=3444 read=4611 -> Gather (cost=52276.29..52276.50 rows=2 width=8) (actual time=1082.180..1095.468 rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 Buffers: shared hit=3444 read=4611 -> Partial Aggregate (cost=51276.29..51276.30 rows=1 width=8) (actual time=897.135..897.138 rows=1 loops=3) Buffers: shared hit=3444 read=4611 -> Parallel Seq Scan on addresses a1 (cost=0.00..51272.61 rows=1472 width=0) (actual time=6.796..894.827 rows=1960 loops=3) Filter: (((geom)::geography && '0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography) AND ('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography && _st_expand((geom)::geography, '25000'::double precision)) AND _st_dwithin('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography, (geom)::geography, '25000'::double precision, true)) Rows Removed by Filter: 86378 Buffers: shared hit=3444 read=4611 Planning Time: 1.167 ms Execution Time: 1095.650 ms
PG13(jit = 關閉)
Finalize Aggregate (cost=2770301.18..2770301.19 rows=1 width=8) (actual time=2801.774..2830.421 rows=1 loops=1) Buffers: shared hit=8227 -> Gather (cost=2770300.97..2770301.18 rows=2 width=8) (actual time=2800.661..2830.384 rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 Buffers: shared hit=8227 -> Partial Aggregate (cost=2769300.97..2769300.98 rows=1 width=8) (actual time=2639.846..2639.849 rows=1 loops=3) Buffers: shared hit=8227 -> Parallel Seq Scan on addresses a1 (cost=0.00..2769300.94 rows=11 width=0) (actual time=63.094..2637.528 rows=1960 loops=3) Filter: st_dwithin('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography, (geom)::geography, '25000'::double precision, true) Rows Removed by Filter: 86378 Buffers: shared hit=8227 Planning Time: 0.424 ms Execution Time: 2830.699 ms
似乎有兩個問題:1/ 未使用空間索引 2/ PG13 在同一台機器上慢 3 倍,相同的數據。
更緊迫的問題是錯誤的查詢。在任何一種情況下都不應超過 1 秒。您的過濾器在
a1."geom"::geography
. 此演員表禁用任何普通索引addresses.geom
- 包括您擁有的空間索引:"idx_addresses_geom" gist (geom)
所以我們
ST_DWithin()
只看到FILTER
它應該是快速辨識合格地址的索引條件。您需要一個表達式索引。看:您的查詢計算給定點 25 公里範圍內的事件數。的第三個參數
ST_DWithin()
是距離。**以米為geography
**單位測量。但對於geometry
,單位由參考系統 (SRID) 定義。PostGis 手冊:For
geometry
:距離以幾何空間參考系統定義的單位指定。要使此函式有意義,源幾何圖形必須位於相同的座標系中(具有相同的 SRID)。您的範例輸入是一個
geography
包含 SRID 4326 的點:SELECT ST_SRID ('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography); -- 4326
SRID 4326 的測量單位是度。使用
geography
(更精確,更昂貴)或使用geometry
不同的 SRID 來繼續使用儀表。解決方案
最好的解決方案取決於更多未公開的資訊。我的範例解決方案對索引和查詢中的轉換幾何進行操作:
CREATE INDEX idx_addresses_geom_27571 ON pg_temp.addresses USING gist (ST_Transform(geom, 27571)); SELECT count(*) -- ① FROM addresses a JOIN events e ON e.physical_address_id = a.id WHERE ST_DWithin(ST_Transform('0101000020E61000001EE1B4E0455F0340E92807B309664840664840'::geometry, 27571) , ST_Transform(a.geom, 27571) , '25000'); -- ②
①小幅改進。假設
events.begins_on
is definedNOT NULL
,count(*)
在這個查詢中是等價的,而且速度要快一點。②描述的主要問題。
我選擇了SRID 27571來適應法國北部的米幾何測量(如您的範例所示)。選擇最合適的參考系統取決於您。
顯然,您只需要轉換還沒有正確 SRID 的輸入幾何。
有關的:
Postgres 13 / PostGIS 中的性能回歸???
對於固定查詢,我希望 Postgres 13 在等效且配置正確的伺服器上比 Postgres 11 快一點。
但為什麼使用 Postgres 11 的原始錯誤查詢更快?
Postgres 11:
Filter: (((geom)::geography && '0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography) AND ('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography && _st_expand((geom)::geography, '25000'::double precision)) AND _st_dwithin('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography, (geom)::geography, '25000'::double precision, true))
Postgres 13:
Filter: st_dwithin('0101000020E61000001EE1B4E0455F0340E92807B309664840'::geography, (geom)::geography, '25000'::double precision, true)
在 Postgres 中,索引支持它綁定到操作符,而不是函式。舊版本的 PostGIS 必須通過使用內聯函式來“破解”系統,以引入索引使用所需的運算符。所以
ST_DWithin(geog1, geog2, 25000)
被替換((geog2 && geog1) AND (geog1 && _st_expand(geog2, 25000)) AND _st_dwithin(geog1, geog2, 25000, true))
為添加邊界框操作符。看:在 Postgres 12 和 PostGIS 3.0 中,整個架構發生了重大變化。引用PostGis 3.0 的發行說明:
4341, 在 PgSQL 12+ 中使用“支持函式”API 代替 SQL 內聯作為在 ST_Intersects 等下提供索引支持的機制
從那時起,應該使用索引的 PostGis 函式具有附加的“支持函式”,而不是使用函式內聯重寫。Postgres手冊:
對於返回的目標函式,
boolean
可以將出現的函式呼叫轉換WHERE
為可索引的運算符子句或子句。轉換後的子句可能完全等同於函式的條件,或者它們可能更弱一些(也就是說,它們可能接受一些函式條件不接受的值)。在後一種情況下,索引條件被稱為有損;它仍然可以用於掃描索引,但是必須為索引返回的每一行執行函式呼叫,以查看它是否真的通過了WHERE
條件。要創建這樣的條件,支持函式必須實現SupportRequestIndexCondition
請求類型。在Postgres 13 和 PostGIS 3.1.3中,我的適配查詢 + 索引(如上所述)的查詢計劃對我來說如下所示:
'Aggregate (cost=52.48..52.48 rows=1 width=8)' ' -> Index Scan using idx_addresses_geom_27571 on addresses a (cost=0.26..52.47 rows=1 width=0)' ' Index Cond: (st_transform(geom, 27571) && st_expand(''0101000020B36B00006E51041D4B8022411FF9F0303F1E3141''::geometry, ''25000''::double precision))' ' Filter: st_dwithin(''0101000020B36B00006E51041D4B8022411FF9F0303F1E3141''::geometry, st_transform(geom, 27571), ''25000''::double precision)'
注意添加的索引條件
(st_transform(geom, 27571) && st_expand(''0101000020B36B00006E51041D4B8022411FF9F0303F1E3141''::geometry, ''25000''::double precision))'
但該Filter
行堅持原來的功能表達式。在沒有索引支持的情況下,在 Postgres 11 中使用邊界框運算符執行重寫的函式表達式似乎比 Postgres 13 中的新實現便宜得多。
要麼您沒有**為 Postgres 13 (???) 使用適當的 PostGIS 版本,**要麼您發現了一個回歸,這可能值得研究。
旁白
timestamp(0) without time zone
可能不是最好的選擇。該表達式對時間戳*進行四捨五入。*四捨五入時,從技術上講,時間戳可能在未來。在同一個事務中執行類似inserted_at <= now()
的操作可能會讓你失敗,而且你永遠也找不到原因。嗯,直到現在。看:
timestamptz
通常也是國際數據的更好選擇。看:
varchar(255)
幾乎沒有意義。看:你有一個單行
geom IS NULL
。您可能會解決這個問題並聲明該列NOT NULL
。