Postgresql

如何調試 Postgres 的加入策略?

  • April 16, 2021

在優化 Postgres 查詢的過程中,我遇到了兩個等效的簡單查詢在非常不同的時間內執行的情況。雖然我無法為此部分提供可重現的範例,但查詢看起來像:

查詢1,快:

select a.id
 from a
 join b on a.id = b.id
 where a.id in ('1', '2', '3')

查詢2,慢:

select a.id
 from a
 join b on a.id = b.id
 where b.id in ('1', '2', '3')

唯一的區別是是否a.idb.idwhere 子句中引用。(如果重要的話,b.iduniquea.id不是。)兩個表都很大,但id列都有索引。

Query 2 執行緩慢的原因是因為 Postgres 使用的是雜湊連接。在查詢 1 中,規劃器選擇了一個嵌套循環,在這樣的選擇性條件下顯然要快得多。

查看查詢計劃(在下面提供)我可以看到預測的選擇性/行數相對準確 - 實際上在較慢的查詢中更準確。換句話說,Postgres 沒有選擇雜湊連接策略,因為它認為條件的結果比實際結果多。

所以,我的問題是:如何調試 Postgres 選擇這種雜湊連接策略的原因?我如何找出為什麼查詢規劃器在呈現查詢 2 時不考慮(或高估)查詢 1 的計劃? EXPLAIN ANALYZE很棒,但它不允許我看到使用的計劃。

查詢計劃

查詢 1(快速):

QUERY PLAN                                                                                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop  (cost=0.99..1528.79 rows=344 width=17) (actual time=0.042..0.210 rows=21 loops=1)                                                                    
 ->  Index Only Scan using a_id_idx on a  (cost=0.56..19.71 rows=344 width=17) (actual time=0.027..0.049 rows=21 loops=1)
       Index Cond: (id = ANY ('{"1","2","3"}'::bpchar[]))                                            
       Heap Fetches: 0                                                                                                                                           
 ->  Index Only Scan using b_pkey on b  (cost=0.43..4.39 rows=1 width=17) (actual time=0.007..0.007 rows=1 loops=21)                   
       Index Cond: (id = (b.id)::bpchar)                                                                                                 
       Heap Fetches: 0                                                                                                                                           
Planning Time: 0.136 ms                                                                                                                                           
Execution Time: 0.227 ms                                                                                                                                          

查詢 2(慢):

QUERY PLAN                                                                                                                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Gather  (cost=1013.81..292692.84 rows=20 width=17) (actual time=704.293..1847.182 rows=21 loops=1)                                                                                             
 Workers Planned: 2                                                                                                                                                                           
 Workers Launched: 2                                                                                                                                                                          
 ->  Hash Join  (cost=13.81..291690.84 rows=8 width=17) (actual time=669.269..1822.987 rows=7 loops=3)                                                                                        
       Hash Cond: ((a.id)::bpchar = b.id)                                                                                                                             
       ->  Parallel Index Only Scan using a_id_idx on a  (cost=0.43..281065.62 rows=4042599 width=17) (actual time=0.019..1086.251 rows=3238563 loops=3)
             Heap Fetches: 0                                                                                                                                                                  
       ->  Hash  (cost=13.34..13.34 rows=3 width=17) (actual time=0.072..0.073 rows=3 loops=3)                                                                                                
             Buckets: 1024  Batches: 1  Memory Usage: 9kB                                                                                                                                     
             ->  Index Only Scan using b_pkey on b  (cost=0.43..13.34 rows=3 width=17) (actual time=0.040..0.068 rows=3 loops=3)                                    
                   Index Cond: (id = ANY ('{"1","2","3"}'::bpchar[]))                                                                  
                   Heap Fetches: 0                                                                                                                                                            
Planning Time: 0.391 ms                                                                                                                                                                        
Execution Time: 1847.219 ms

我如何找出為什麼查詢規劃器在呈現查詢 2 時不考慮(或高估)查詢 1 的計劃?EXPLAIN ANALYZE很棒,但它不允許我看到使用的計劃weren't

您可以“禁用”各種計劃器方法以查看替代計劃。引用“禁用”,因為那些方法實際上並沒有被禁用,只是估計高得離譜,因此任何其他計劃似乎都是有利的。**僅用於調試!**不適合永久使用。

手冊中的Planner Method Configuration一章中有一個列表。考慮頂部的建議:

這些配置參數提供了一種影響查詢優化器選擇的查詢計劃的粗略方法。如果優化器為特定查詢選擇的預設計劃不是最優的, 臨時解決方案是使用這些配置參數之一來強制優化器選擇不同的計劃。提高優化器選擇的計劃質量的更好方法包括調整計劃器成本常量(參見第 19.7.2 節)、ANALYZE手動執行、增加 default_statistics_target配置參數的值以及使用ALTER TABLE SET STATISTICS.

在您的特定情況下,要了解 Postgres 選擇此計劃而不是其他計劃的原因,請比較禁用散列連接策略之前和之後的估計成本。劇透警報:這是因為它(錯誤地)預測成本更低。例如:

EXPLAIN <Query 2>;  -- take note
SET enable_hashjoin = off;
EXPLAIN <Query 2>;  -- compare

或使用EXPLAIN (ANALYZE, BUFFERS)以獲取更多詳細資訊。你似乎很熟悉EXPLAIN

將選擇另一個計劃,估計成本會更高。一個或兩個估計與現實相去甚遠。為什麼Postgres 得出錯誤的估計是另一個問題。基本入門者在上述手冊的建議中。

也許您的安裝(也)對並行成本過於樂觀。有許多GUC 參數可以調整它。您可以禁用並行性以進行測試:

SET max_parallel_workers_per_gather = 0;

強制特定的連接順序?

對於評論中添加的問題:

查看以相反順序在嵌套循環中執行掃描的成本?

您可以嘗試join_collapse_limit

將其設置為 1 可防止顯式 JOIN 的任何重新排序。因此,查詢中指定的顯式連接順序將是連接關係的實際順序。因為查詢計劃器並不總是選擇最佳連接順序,高級使用者可以選擇暫時將此變數設置為 1,然後明確指定他們想要的連接順序。有關詳細資訊,請參閱第 14.3 節

所以:

SET join_collapse_limit = 1;
EXPLAIN <Query 2>; 
EXPLAIN <Query 2 with tables reversed>;  -- compare

有關的:

不要使用數據類型char(n)

設置中的一個可避免的問題在EXPLAIN計劃中添加的演員表中變得很明顯:

 Index Cond: (id = (b.id)::bpchar)

等等。

至少有一id列是類型characterbpchar是過時 SQL 數據類型的內部名稱character。“bpchar”代表“空白填充字元”。永遠不要使用它。尤其不適用於 PK 列。類型character是你一見鍾情就射中頭部的殭屍。

不要將character/ char/bpchar與有用的數據類型textvarchar"char"(帶引號!)混淆。看:

如果您的 id 列包含整數,請使用integer(或bigint)。其他text或任何合適的。

可能會導致較差的查詢計劃。

結果成了主要問題。請參閱OP 對錯誤報告的跟進

引用自:https://dba.stackexchange.com/questions/289827