在PostgreSQL中修改了一行不明顯的代碼,把(ANY(ARRAY[...]) 改爲 ANY(VALUES(...))),結果查詢時間從20s變爲0.2s。最初咱們學習使用EXPLAN ANALYZE來優化代碼,到後來,Postgres社區也成爲咱們學習提高的一個好幫手,付出總會有回報,咱們產品的性能也所以獲得了極大的提高。html
事出有因java
咱們所開發的產品是Datadog,它是專門爲那些編寫和運營大規模應用的團隊、IT運營商提供監控服務的一個平臺,幫助他們把海量的數據轉化爲切實可行的計劃、操做方案。而在這周早些時候,咱們的許多數據庫所面臨的一個性能問題是在一個較小的表上進行大量的key查詢。這些查詢中的99.9%都是高效靈活的。在極少數實例中,有些數量的性能指標tag查詢是費時的,這些查詢須要花費20s時間。這也就意味着用戶須要在瀏覽器面前花費這麼長的時間來等待圖形編輯器作出響應。即便是0.1%,這樣的用戶體驗也顯然糟透了,對此,咱們進行了監測,探究爲什麼速度會這麼慢。sql
查詢與計劃數據庫
結果使人震驚,罪魁禍首居然是下面這個簡單的查詢:數組
1
2
3
4
5
6
7
8
9
10
|
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (ARRAY[
15368196
, --
11
,
000
other keys --)])
AND c.x_key =
1
AND c.tags @> ARRAY[E
'blah'
];
|
X表擁有上千行數據,C表擁有1500萬行數據,這兩個表的「key」列都帶有適當的索引主鍵。簡單地說,它就是一個簡單的主鍵查詢。但有趣地是,隨着key列中記錄的增長,例如在11000行時,咱們經過添加EXPLAIN (ANALYZE, BUFFERS)前綴來查看key列的值是否與數組中的值匹配:瀏覽器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Nested Loop (cost=
6923.33
..
11770.59
rows=
1
width=
362
) (actual time=
17128.188
..
22109.283
rows=
10858
loops=
1
)
Buffers: shared hit=
83494
-> Bitmap Heap Scan on context c (cost=
6923.33
..
11762.31
rows=
1
width=
329
) (actual time=
17128.121
..
22031.783
rows=
10858
loops=
1
)
Recheck Cond: ((tags @>
'{blah}'
::text[]) AND (x_key =
1
))
Filter: (key = ANY (
'{15368196,(a lot more keys here)}'
::integer[]))
Buffers: shared hit=
50919
-> BitmapAnd (cost=
6923.33
..
6923.33
rows=
269
width=
0
) (actual time=
132.910
..
132.910
rows=
0
loops=
1
)
Buffers: shared hit=
1342
-> Bitmap Index Scan on context_tags_idx (cost=
0.00
..
1149.61
rows=
15891
width=
0
) (actual time=
64.614
..
64.614
rows=
264777
loops=
1
)
Index Cond: (tags @>
'{blah}'
::text[])
Buffers: shared hit=
401
-> Bitmap Index Scan on context_x_id_source_type_id_idx (cost=
0.00
..
5773.47
rows=
268667
width=
0
) (actual time=
54.648
..
54.648
rows=
267659
loops=
1
)
Index Cond: (x_id =
1
)
Buffers: shared hit=
941
-> Index Scan using x_pkey on x (cost=
0.00
..
8.27
rows=
1
width=
37
) (actual time=
0.003
..
0.004
rows=
1
loops=
10858
)
Index Cond: (x.key =
1
)
Buffers: shared hit=
32575
Total runtime:
22117.417
ms
|
在最低水平,這些查詢看起來就像是這些CPU利用率的峯值。在這裏主要是想證明一個關鍵點:數據庫不會等待磁盤去讀取數據,而是作排序、散列和行比較這些事。編輯器
經過Postgres獲取與峯值最接近的行數。ide
顯然,咱們的查詢在大多數狀況下都有條不紊的執行着。oop
Postgres的性能問題:位圖堆掃描
post
rows_fetched度量與下面的部分計劃是一致的:
1
2
3
4
5
|
Buffers: shared hit=
83494
-> Bitmap Heap Scan on context c (cost=
6923.33
..
11762.31
rows=
1
width=
329
) (actual time=
17128.121
..
22031.783
rows=
10858
loops=
1
)
Recheck Cond: ((tags @>
'{blah}'
::text[]) AND (x_key =
1
))
Filter: (key = ANY (
'{15368196,(a lot more keys here)}'
::integer[]))
Buffers: shared hit=
50919
|
幸運的是,咱們的表有30%都是裝載在RAM中,因此在從磁盤上檢查行的時候,它不會表現的太糟糕。但在性能上,它仍然存在很是明顯的影響。查詢過於簡單,這是一個很是簡單的key查找,因此沒有顯而易見的數據庫或應用重構,它很難找到一些簡單的方式來解決這個問題。最後,咱們使用PGSQL-Performance郵件向社區求助。
解決方案
開源幫了咱們,經驗豐富的且代碼貢獻量很是多的Tom Lane讓咱們試試這個:
1
2
3
4
5
6
7
8
9
10
|
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (VALUES (
15368196
), --
11
,
000
other keys --)
AND c.x_key =
1
AND c.tags @> ARRAY[E
'blah'
];
|
咱們使用ARRAY[...]列舉出全部的關鍵字來進行查詢,但卻欺騙了查詢優化器。Values(...)讓優化器充分使用關鍵字索引。僅僅是一行代碼的改變,而且沒有產生任何語義的改變。
下面是新查詢語句的寫法,差異就在於第三和第十四行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Nested Loop (cost=
168.22
..
2116.29
rows=
148
width=
362
) (actual time=
22.134
..
256.531
rows=
10858
loops=
1
)
Buffers: shared hit=
44967
-> Index Scan using x_pkey on x (cost=
0.00
..
8.27
rows=
1
width=
37
) (actual time=
0.071
..
0.073
rows=
1
loops=
1
)
Index Cond: (id =
1
)
Buffers: shared hit=
4
-> Nested Loop (cost=
168.22
..
2106.54
rows=
148
width=
329
) (actual time=
22.060
..
242.406
rows=
10858
loops=
1
)
Buffers: shared hit=
44963
-> HashAggregate (cost=
168.22
..
170.22
rows=
200
width=
4
) (actual time=
21.529
..
32.820
rows=
11215
loops=
1
)
-> Values Scan on
"*VALUES*"
(cost=
0.00
..
140.19
rows=
11215
width=
4
) (actual time=
0.005
..
9.527
rows=
11215
loops=
1
)
-> Index Scan using context_pkey on context c (cost=
0.00
..
9.67
rows=
1
width=
329
) (actual time=
0.015
..
0.016
rows=
1
loops=
11215
)
Index Cond: (c.key =
"*VALUES*"
.column1)
Filter: ((c.tags @>
'{blah}'
::text[]) AND (c.x_id =
1
))
Buffers: shared hit=
44963
Total runtime:
263.639
ms
|
產品裏新的查詢
部署後的代碼:
數據庫看起來更美觀
Postgres慢查詢將一去不復返了。但有誰願意由於這個0.1%的倒黴蛋再去折磨呢?咱們使用Datadog來驗證修改是否正確,它可以作出即時驗證。若是你想查看Postgres查詢速度的各類影響, 不妨試試Datadog吧。