Postgresql

Postgres多連接慢查詢,如何儲存預設子記錄

  • May 4, 2021

我在 Postgres 中有一個小型數據庫,大約 10,000 條記錄儲存公司客戶。

我有一個執行非常頻繁的“緩慢”執行查詢(大約半秒),我的老闆希望我改進它。

首先 - 我的程式碼:

select customer_alias.id, customer_alias.name, site.address, phone.phonenumber
from customer_alias
join customer on customer_alias.customer_id = customer.id
left join site on customer.default_site_id = site.id
left join contact_phonenumbers as phone on site.default_phonenumber = phone.id

編輯left join customerjoin customer

customer我突然想到的是,即使我沒有從該記錄中選擇任何內容,我也正在執行加入。我目前必須加入它才能獲得表default_site_id的外鍵site

每個客戶可以有多個站點,但在此列表中只能顯示一個(必須打開一個客戶才能查看所有站點)。所以我的問題是,如果我無法優化查詢,是否有不同的方式可以為特定客戶儲存預設站點?預設電話號碼也是如此

一個客戶可以有多個站點,但一個站點只有一個客戶(多對一)。


EXPLAIN返回:

Hash Join  (cost=522.72..943.76 rows=5018 width=53)
 Hash Cond: (customer.id = customer_alias.customer_id)
 ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32)
       Hash Cond: (site.id = customer.default_site_id)
       ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32)
             Hash Cond: (phone.id = site.default_phonenumber)
             ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17)
             ->  Hash  (cost=122.18..122.18 rows=5018 width=23)
                   ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23)
       ->  Hash  (cost=124.18..124.18 rows=5018 width=8)
             ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8)
 ->  Hash  (cost=88.18..88.18 rows=5018 width=29)
       ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29)

EXPLAIN ANALYZE返回:

Hash Join  (cost=522.72..943.76 rows=5018 width=53) (actual time=12.457..26.655 rows=5018 loops=1)
 Hash Cond: (customer.id = customer_alias.customer_id)
 ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32) (actual time=8.589..18.796 rows=5018 loops=1)
       Hash Cond: (site.id = customer.default_site_id)
       ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32) (actual time=4.499..11.067 rows=5018 loops=1)
             Hash Cond: (phone.id = site.default_phonenumber)
             ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17) (actual time=0.007..1.581 rows=6970 loops=1)
             ->  Hash  (cost=122.18..122.18 rows=5018 width=23) (actual time=4.465..4.465 rows=5018 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 277kB
                   ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23) (actual time=0.007..2.383 rows=5018 loops=1)
       ->  Hash  (cost=124.18..124.18 rows=5018 width=8) (actual time=4.072..4.072 rows=5018 loops=1)
             Buckets: 1024  Batches: 1  Memory Usage: 197kB
             ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8) (actual time=0.009..2.270 rows=5018 loops=1)
 ->  Hash  (cost=88.18..88.18 rows=5018 width=29) (actual time=3.855..3.855 rows=5018 loops=1)
       Buckets: 1024  Batches: 1  Memory Usage: 309kB
       ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29) (actual time=0.008..1.664 rows=5018 loops=1)
Total runtime: 27.290 ms"

表結構


/* ---------------------------------------------------------------------------
* 表:contacts.customer
* -------------------------------------------------------------------------*/
創建表contacts.customer(
id SERIAL NOT NULL,
name integer, -- contacts.customer_alias 的外鍵
default_site_id 整數,--contacts.site 的外鍵
-- 其他12個與查詢無關的欄位
約束 customer_pkey 主鍵 (id)
約束 default_site_id 外鍵 (default_site_id)
參考 contacts.site (id) MATCH SIMPLE
更新時不採取行動 刪除時不採取行動;
);

-- 稍後添加到 customer_alias 的循環外鍵

-- 索引: id, name, default_site_id (btree)


/*----------------------------------------------------------------------------
* 表:contacts.customer_alias
*--------------------------------------------------------------------------*/
創建表contacts.customer_alias (
id SERIAL NOT NULL,
customer_id 整數,
名稱文本,
-- 5 個其他欄位(未在查詢中使用)
CONSTRAINT customer_alias_pkey PRIMARY KEY (id),
約束 customer_alias_customer_id_fkey FOREIGN KEY (customer_id)
參考 contacts.customer (id) MATCH SIMPLE
更新 無操作 刪除 無操作 可延期 初始延期
)
-- 索引: id, customer_id (btree)

-- 客戶別名外鍵
ALTER TABLE contacts.customer
添加約束 customer_name_fkey 外鍵(名稱)
REFERENCES contacts.customer_alias (id) MATCH SIMPLE
更新時不採取行動 刪除時不採取行動;


/* ---------------------------------------------------------------------------
* 表:contacts.site
* -------------------------------------------------------------------------*/
創建表contacts.site(
id SERIAL NOT NULL,
customer_id 整數,
地址文字,
default_contact_id 整數,
default_phonenumber 整數,
-- 其他9個不相關的領域
CONSTRAINT site_pkey PRIMARY KEY (id),
約束 site_customer_id_fkey FOREIGN KEY (customer_id)
參考 contacts.customer (id) MATCH SIMPLE
更新時無操作 刪除時無操作
)
索引:id、customer_id、default_contact_id、default_phonenumber(btree)


/* ---------------------------------------------------------------------------
* 表:contacts.contact_phonenumbers
* -------------------------------------------------------------------------*/
CREATE TABLE 聯繫人.contact_phonenumbers (
id SERIAL NOT NULL,
site_id 整數,
電話號碼文字,
-- 4 個其他不相關的領域
約束contact_phonenumbers_pkey PRIMARY KEY (id)
)
-- 索引: id, site_id (btree)

如果我通過 ODBC 執行查詢客戶端,則需要 450-500 毫秒。如果我在 pgAdmin III 中執行查詢,它指出查詢大約需要 250 毫秒,儘管有時需要 60-100 毫秒(這是我的目標)。

我目前沒有對伺服器的 SSH 訪問權限,因此無法直接執行它。

我在螢幕上的任何時候都只能查看大約 100 行,是否可以只檢索相關的行?例如LIMIT 100, OFFSET 2345,我嘗試限制結果,但每次都會執行新的搜尋。

非常感謝到目前為止的幫助!

你寫:

每個客戶可以有多個站點,但在此列表中應該只顯示一個。

然而,您的查詢檢索所有行。這將是一個優化點。但是您也沒有定義site要選擇哪個。

無論哪種方式,這都無關緊要。您EXPLAIN的掃描僅顯示 5026 行site(掃描為 5018行customer)。因此,幾乎沒有任何客戶實際上擁有多個站點。跑步前你ANALYZE的桌子了EXPLAIN嗎?

從我在您的 中看到的數字來看EXPLAIN索引不會為您提供此查詢的任何資訊。順序表掃描將是最快的方法。不過,對於 5000 行來說,半秒相當慢。也許您的數據庫需要一些一般的性能調整

也許查詢本身更快,但“半秒”包括網路傳輸?解釋分析會告訴我們更多。

如果這個查詢是你的瓶頸,我建議你實現一個物化視圖


在您提供更多資訊後,我發現我的診斷幾乎成立。

查詢本身需要 27 毫秒。那裡問題不大。“半秒鐘”是我懷疑的那種誤解。緩慢的部分是網路傳輸(加上 ssh 編碼/解碼,可能是渲染)。您應該只檢索 100 行,這將解決大部分問題,即使這意味著每次都執行整個查詢。

如果您像我建議的那樣使用物化視圖走這條路線,您可以在表中添加一個**沒有間隙的序列號,**並在其上添加索引 - 通過添加一列row_number() OVER (<your sort criteria here>) AS mv_id

然後可以查詢:

SELECT *
FROM   materialized_view
WHERE  mv_id >= 2700
AND    mv_id <  2800;

這將執行得非常快。LIMIT/OFFSET無法競爭,需要計算整個表才能排序和選擇 100 行。

pgAdmin3 計時

當您從查詢工具執行查詢時,消息窗格顯示如下內容:

Total query runtime: 62 ms.

並且狀態行顯示同時。我引用 pgAdmin3 對此的幫助:

狀態行將顯示上次查詢完成所用的時間。如果返回數據集,則不僅會顯示伺服器執行的經過時間,還會顯示從伺服器檢索數據到數據輸出頁面的時間。

如果您想查看伺服器上的時間,您需要使用 SQLEXPLAIN ANALYZE或內置Shift + F7鍵盤快捷鍵或Query -> Explain analyze. 然後,在解釋輸出的底部,您會得到如下內容:

Total runtime: 0.269 ms

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