小心帶來緩衝區的操作...
以一個小查詢為例,讓我們來看看一些在 PostgreSQL 中最佳化查詢的通用方法。 是否使用它們取決於您自己,但了解它們是值得的。
在 PG 的某些後續版本中,隨著調度程序變得更加智能,情況可能會發生變化,但對於 9.4/9.6,情況看起來大致相同,如這裡的範例所示。
讓我們提出一個非常實際的請求:
SELECT
TRUE
FROM
"Документ" d
INNER JOIN
"ДокументРасширение" doc_ex
USING("@Документ")
INNER JOIN
"ТипДокумента" t_doc ON
t_doc."@ТипДокумента" = d."ТипДокумента"
WHERE
(d."Лицо3" = 19091 or d."Сотрудник" = 19091) AND
d."$Черновик" IS NULL AND
d."Удален" IS NOT TRUE AND
doc_ex."Состояние"[1] IS TRUE AND
t_doc."ТипДокумента" = 'ПланРабот'
LIMIT 1;
關於表名和欄位名欄位和表格的“俄語”名稱可以不同地對待,但這是一個品味問題。 因為
讓我們看看最終的計劃:
144ms 和近 53K 緩衝區 ——也就是超過400MB的數據! 如果在我們請求時所有這些都在快取中,我們將很幸運,否則從磁碟讀取時將花費數倍的時間。
演算法最重要!
為了以某種方式優化任何請求,您必須先了解它應該做什麼。
我們暫時將資料庫結構本身的開發放在本文的討論範圍之外,並同意我們可以相對“便宜” 重寫請求 和/或將一些我們需要的東西滾到底座上 指標.
所以請求:
— 檢查至少一些文件的存在
- 在我們需要的條件下並且是某種類型
- 作者或表演者是我們需要的員工
加入+限制1
通常,開發人員更容易編寫一個查詢,其中首先連接大量表,然後整個集合中僅保留一筆記錄。 但對於開發人員來說更容易並不代表資料庫更有效率。
在我們的例子中,只有 3 張桌子 - 效果如何...
我們先去掉與「文檔類型」表的連接,同時告訴資料庫: 我們的類型記錄是獨一無二的 (我們知道這一點,但調度程序還不知道):
WITH T AS (
SELECT
"@ТипДокумента"
FROM
"ТипДокумента"
WHERE
"ТипДокумента" = 'ПланРабот'
LIMIT 1
)
...
WHERE
d."ТипДокумента" = (TABLE T)
...
是的,如果表/CTE 由單一記錄的單一欄位組成,那麼在 PG 中你甚至可以這樣寫,而不是
d."ТипДокумента" = (SELECT "@ТипДокумента" FROM T LIMIT 1)
PostgreSQL 查詢中的惰性求值
BitmapOr 與 UNION
在某些情況下,點陣圖堆掃描會讓我們付出很大的代價——例如,在我們的情況下,當相當多的記錄滿足所需條件時。 我們得到它是因為 OR 條件變成 BitmapOr- 按計畫運作。
讓我們回到最初的問題——我們需要找到一筆對應的記錄 任何人 從條件來看-即兩種條件下都不需要搜尋所有59K記錄。 有一種方法可以解決一個條件,並且 僅當第一個中沒有找到任何內容時才轉到第二個。 下面的設計將幫助我們:
(
SELECT
...
LIMIT 1
)
UNION ALL
(
SELECT
...
LIMIT 1
)
LIMIT 1
「外部」LIMIT 1 確保在找到第一個記錄時結束搜尋。 如果已經在第一個區塊中找到,則不會執行第二個區塊(從未被執行過 方面)。
“CASE下隱藏困難條件”
原始查詢中有一個非常不方便的時刻 - 根據相關表格「DocumentExtension」檢查狀態。 無論表達式中其他條件的真實性如何(例如, d.「已刪除」不屬實),這個連線總是被執行並且「消耗資源」。 或多或少將被花費——取決於這張桌子的大小。
但是您可以修改查詢,以便僅在確實有必要時才搜尋相關記錄:
SELECT
...
FROM
"Документ" d
WHERE
... /*index cond*/ AND
CASE
WHEN "$Черновик" IS NULL AND "Удален" IS NOT TRUE THEN (
SELECT
"Состояние"[1] IS TRUE
FROM
"ДокументРасширение"
WHERE
"@Документ" = d."@Документ"
)
END
一旦從連結表給我們 結果不需要任何字段,那麼我們就有機會將 JOIN 轉換為子查詢的條件。
讓我們將索引欄位保留在“CASE 括號之外”,將記錄中的簡單條件添加到 WHEN 區塊 - 現在,僅在傳遞給 THEN 時才會執行“重”查詢。
我的姓氏是“總”
我們使用上述所有機制收集結果查詢:
WITH T AS (
SELECT
"@ТипДокумента"
FROM
"ТипДокумента"
WHERE
"ТипДокумента" = 'ПланРабот'
)
(
SELECT
TRUE
FROM
"Документ" d
WHERE
("Лицо3", "ТипДокумента") = (19091, (TABLE T)) AND
CASE
WHEN "$Черновик" IS NULL AND "Удален" IS NOT TRUE THEN (
SELECT
"Состояние"[1] IS TRUE
FROM
"ДокументРасширение"
WHERE
"@Документ" = d."@Документ"
)
END
LIMIT 1
)
UNION ALL
(
SELECT
TRUE
FROM
"Документ" d
WHERE
("ТипДокумента", "Сотрудник") = ((TABLE T), 19091) AND
CASE
WHEN "$Черновик" IS NULL AND "Удален" IS NOT TRUE THEN (
SELECT
"Состояние"[1] IS TRUE
FROM
"ДокументРасширение"
WHERE
"@Документ" = d."@Документ"
)
END
LIMIT 1
)
LIMIT 1;
調整索引
訓練有素的眼睛注意到 UNION 子區塊中的索引條件略有不同 - 這是因為我們在表上已經有了合適的索引。 如果它們不存在,那麼就值得創建: 文檔(Person3,文檔類型) и 文檔(文檔類型,員工).
關於 ROW 條件中欄位的順序從策劃者的角度來看,當然可以這樣寫 (A, B) = (常數A, 常數B)和 (B, A) = (常數B, 常數A)。 但錄音時 依照索引中欄位的順序,這樣的請求只是為了以後調試更方便。
計劃中有什麼?
不幸的是,我們運氣不好,第一個 UNION 區塊中什麼也沒找到,所以第二個 UNION 區塊仍然被執行。 但即便如此——也只是 0.037ms 和 11 個緩衝區!
我們加快了請求速度並減少了記憶體中的資料泵送 幾千次,使用相當簡單的技術 - 只需少量複製貼上即可獲得良好的結果。 🙂
來源: www.habr.com