Postgresql

PostgreSQL 似乎在簡單的條件連接中創建了低效的計劃

  • February 1, 2016

考慮以下兩個查詢:

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 正在創建兩個不同的計劃,而第二個計劃要好得多:

我非常喜歡第一個查詢,因為我想從連接創建一個視圖並將 where 條件放在視圖上的查詢上,在那裡我看到一個 id 列(我使用連接,USING因此單個 id 列在視圖中可見) . 此外,我將加入兩個以上的表,並且我不希望為每個加入添加這樣的條件。

這種行為有什麼原因嗎?或者它是一個錯誤?有什麼解決方法嗎?

  • 替換ON t1.id = t2.idUSING (id)在兩個查詢中都沒有區別。
  • 這是 PostgreSQL 9.3
  • 實際返回的行數為 17,658,189
  • 分析已在表上執行。但是,PostgreSQL 的統計相關設置是其預設值。
  • 觀察:explain for query 1 對最終結果有很好的估計,但對查詢 t2 使用了較差的計劃。對於第二個查詢,從 t1 和 t2 估計的行數很好,但對最終合併的估計大約是實際行數的一半。
  • id列是兩個表中的主鍵。表有大約 350,000,000 行。t1 約為 20GiB,而 t2 約為 14GiB。
  • 替換INNER JOINLEFT 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
)

然後它看起來像優化器的盲點,您應該使用第二個查詢。

當有一個條件連接兩個表ab: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來修復這個問題。我在提出解決方案時遇到的問題是,我無法報告有關有多少人受到此規劃器限制的詳細資訊。我提出的更新檔對許多查詢的計劃時間造成了非常小的影響,並且許多人認為它不適用於足夠的情況,因為它值得放慢不可能受益的查詢。我當然同意這一點,我對放慢查詢計劃的速度沒有興趣,但同時理解這方面令人討厭的糟糕優化。

儘管請記住我提出的更新檔只是提案的初稿。不用於生產用途。

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