PostgreSQL 似乎在簡單的條件連接中創建了低效的計劃
考慮以下兩個查詢:
SELECT t1.id, * FROM t1 INNER JOIN t2 ON t1.id = t2.id where t1.id > -9223372036513411363;
和:
SELECT t1.id, * FROM t1 INNER JOIN t2 ON t1.id = t2.id where t1.id > -9223372036513411363 and t2.id > -9223372036513411363;
**注意:**不是表中的
-9223372036513411363
最小值,並且條件將結果(從總行數 3.5 億)減少到 1700 萬。就個人而言,我希望 PostgreSQL 為這兩個查詢提供相同的計劃,因為
t1.id = t2.id
自動暗示了第二個條件。但不幸的是,PostgreSQL 正在創建兩個不同的計劃,而第二個計劃要好得多:
- 第一次查詢:http ://explain.depesz.com/s/uauk
- 第二個查詢:連結:http ://explain.depesz.com/s/uQd
- 第二個查詢的解釋分析:http : //explain.depesz.com/s/Snkx (第二個查詢在 215 秒內完成,而第一個查詢在 1000 秒後沒有完成,直到我終止它)。
我非常喜歡第一個查詢,因為我想從連接創建一個視圖並將 where 條件放在視圖上的查詢上,在那裡我看到一個 id 列(我使用連接,
USING
因此單個 id 列在視圖中可見) . 此外,我將加入兩個以上的表,並且我不希望為每個加入添加這樣的條件。這種行為有什麼原因嗎?或者它是一個錯誤?有什麼解決方法嗎?
- 替換
ON t1.id = t2.id
為USING (id)
在兩個查詢中都沒有區別。- 這是 PostgreSQL 9.3
- 實際返回的行數為 17,658,189
- 分析已在表上執行。但是,PostgreSQL 的統計相關設置是其預設值。
- 觀察:explain for query 1 對最終結果有很好的估計,但對查詢 t2 使用了較差的計劃。對於第二個查詢,從 t1 和 t2 估計的行數很好,但對最終合併的估計大約是實際行數的一半。
- 該
id
列是兩個表中的主鍵。表有大約 350,000,000 行。t1 約為 20GiB,而 t2 約為 14GiB。- 替換
INNER JOIN
為LEFT OUTER JOIN
產生類似的結果- 選擇更少的行(通過增加 where 條件中的最小 ID 值)不會產生任何差異,直到行數變得太少,在這種情況下它使用完全不同的計劃。
我想要達到的目標
我有一個包含很多行的數據庫,並且不斷向其中插入新數據。我們希望為這些數據生成不同的報告,其中包括不同類型的查詢,例如:搜尋不同的數據、按每列排序、聚合查詢等。
在目前設計中,我們沒有 UPDATE 操作。目前,我正在嘗試一種高度規範化的設計(基於 Anchor 建模和/或 6NF 提出的想法)。這樣的設計將廣泛使用 JOIN 和 VIEW 以使使用 DB 變得愉快,因此需要數據庫能夠有效地完成這些工作。
據我所知(基於這樣的問題),PostgreSQL 似乎不太適合這種設計(大約有 11 個表和許多視圖)並且似乎總是比不那麼規範化的設計表現更差有一個或兩個表,沒有視圖。我希望計劃 JOIN 查詢中的這個問題是我的錯,但似乎還沒有。有了這個問題,我似乎應該忘記使用 VIEWS 並使用具有大量重複條件的詳細查詢,或者忘記使用 PostgreSQL 或這種設計。
表
實際的列數要多一些,但它們與其他表沒有任何關係,因此與本次討論無關:
CREATE TABLE t1 ( id bigint NOT NULL DEFAULT nextval('ids_seq'::regclass), total integer NOT NULL, price integer NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY (id) ) CREATE TABLE t2 ( id bigint NOT NULL, category smallint NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY (id), CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES t1 (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION )
然後它看起來像優化器的盲點,您應該使用第二個查詢。
當有一個條件連接兩個表
a
和b
:a.id = b.id
和一個附加條件時a.id > @some_constant
,似乎優化器使用“索引條件”來開始對索引進行索引掃描,a (id)
但它沒有將它用於第二個索引b (id)
。因此,添加 (redundant)
b.id > @some_constant
允許它生成稍微更有效的計劃,同時也跳過b (id)
索引的一部分。這可以作為改進建議(如果還沒有的話)發布給 Postgres 黑客組。
編輯後,我們知道它有一個
FOREIGN KEY
約束。因此,編寫查詢的“自然”(等效)方式是:t2``REFERENCES t1
SELECT -- whatever FROM t2 LEFT JOIN t1 ON t1.id = t2.id WHERE t2.id > -9223372036513411363 ;
你能試試這個並告訴我們它產生的執行計劃嗎?有些轉換僅適用於
LEFT
(外部)連接而不適用於內部連接。不幸的是,這也不會產生任何不同的計劃。
OP 在 Postgres 性能列表中發布了一個問題,我們可以在此處查看整個執行緒:PostgreSQL 似乎在簡單的條件連接中創建低效計劃和 David Rowley 的回复,這證實了這是一個雖然已經被考慮過的功能,尚未在優化器中實現:
**是的,不幸的是,您已經完成了唯一可以做的事情,那就是在查詢中包含這兩個條件。**是否有一些特殊原因導致您不能只
t2.id > ...
在查詢中寫入條件?或者查詢是由您無法控制的某些軟體動態生成的?我個人非常希望看到這方面的改進,甚至寫了一個更新檔1來修復這個問題。我在提出解決方案時遇到的問題是,我無法報告有關有多少人受到此規劃器限制的詳細資訊。我提出的更新檔對許多查詢的計劃時間造成了非常小的影響,並且許多人認為它不適用於足夠的情況,因為它值得放慢不可能受益的查詢。我當然同意這一點,我對放慢查詢計劃的速度沒有興趣,但同時理解這方面令人討厭的糟糕優化。
儘管請記住我提出的更新檔只是提案的初稿。不用於生產用途。