Postgres多連接慢查詢,如何儲存預設子記錄
我在 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 customer
為join 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 對此的幫助:
狀態行將顯示上次查詢完成所用的時間。如果返回數據集,則不僅會顯示伺服器執行的經過時間,還會顯示從伺服器檢索數據到數據輸出頁面的時間。
如果您想查看伺服器上的時間,您需要使用 SQL
EXPLAIN ANALYZE
或內置Shift + F7
鍵盤快捷鍵或Query -> Explain analyze
. 然後,在解釋輸出的底部,您會得到如下內容:Total runtime: 0.269 ms