轉PostgreSQL 用游標優化的一個例子


一位PG社區的朋友提到的一個應用場景,目前遇到性能問題。
數據結構大概是這樣的,包含一個主鍵,一個數組,一個時間,其他字段。
請求分析:
有檢索需求,比較頻繁。查找數組中包含某些元素的記錄,並按時間排序輸出所有符合條件的記錄,檢索到的符合條件的記錄可能上萬條,也可能較少。
有插入需求,量不大。
有更新需求,一條記錄最多一天會被更新一次,當然也可能不會被更新。
無刪除需求。
數據量在千萬級別。

這個應用場景的不安定因素來自於一些熱點值。
例如,當輸出的數據量較大時,排序對CPU的開銷較大。而這些熱點值可能也是查詢的熱點。
對於檢索的條件是數組,這個可以用GIN索引來解決,只有排序是無法解決的。

測試,生成300萬測試記錄: postgres=# create table test(id int primary key,info int[],crt_date date); CREATE TABLE postgres=# insert into test select generate_series(1,3000000), ('{'||round(random()*1000)||','||round(random()*1000)||','||round(random()*1000)||'}')::int[], current_date+round(random()*1000)::int; INSERT 0 3000000 postgres=# create index idx_test_info on test using gin(info); CREATE INDEX 當輸出記錄較少時,效率還是可以的,例如以下: postgres=# explain (analyze,verbose,buffers,timing) select info,crt_date from test where info @> '{1,8}'::int[] order by crt_date desc; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Sort (cost=101.23..101.29 rows=22 width=37) (actual time=1.668..1.672 rows=21 loops=1) Output: info, crt_date Sort Key: test.crt_date DESC Sort Method: quicksort Memory: 26kB Buffers: shared hit=26 -> Bitmap Heap Scan on public.test (cost=16.17..100.74 rows=22 width=37) (actual time=1.609..1.647 rows=21 loops=1) Output: info, crt_date Recheck Cond: (test.info @> '{1,8}'::integer[]) Heap Blocks: exact=21 Buffers: shared hit=26 -> Bitmap Index Scan on idx_test_info (cost=0.00..16.17 rows=22 width=0) (actual time=1.595..1.595 rows=21 loops=1) Index Cond: (test.info @> '{1,8}'::integer[]) Buffers: shared hit=5 Planning time: 0.224 ms Execution time: 1.722 ms (15 rows) 返回21行,算上排序需要1.7毫秒。 但是如果返回記錄數上萬之后,來看看結果: postgres=# explain (analyze,verbose,buffers,timing) select info,crt_date from test where info @> '{1}'::int[] order by crt_date desc; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------- Sort (cost=7737.83..7754.58 rows=6700 width=37) (actual time=17.726..18.856 rows=8896 loops=1) Output: info, crt_date Sort Key: test.crt_date DESC Sort Method: quicksort Memory: 1080kB Buffers: shared hit=5028 -> Bitmap Heap Scan on public.test (cost=59.93..7312.04 rows=6700 width=37) (actual time=3.722..13.585 rows=8896 loops=1) Output: info, crt_date Recheck Cond: (test.info @> '{1}'::integer[]) Heap Blocks: exact=5025 Buffers: shared hit=5028 -> Bitmap Index Scan on idx_test_info (cost=0.00..58.25 rows=6700 width=0) (actual time=2.620..2.620 rows=8896 loops=1) Index Cond: (test.info @> '{1}'::integer[]) Buffers: shared hit=3 Planning time: 0.151 ms Execution time: 19.637 ms (15 rows) 返回8896行,算上排序需要19.6毫秒。(這是返回所有記錄的時間,如果是分頁的話,第一頁會很快返回) 優化建議。 1. 如果遇到排序帶來的CPU負載過高的問題,可以創建熱值partial index 對於熱值,創建partial index。例如以上熱值: postgres=# create index idx_test_info_1 on test (crt_date) where info @> '{1}'::int[]; CREATE INDEX 禁止排序 postgres=# set enable_sort=off; SET postgres=# explain (analyze,verbose,buffers,timing) select * from test where info @> '{1}'::int[] order by crt_date desc; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------ ------------- Index Scan Backward using idx_test_info_1 on public.test (cost=0.29..18253.53 rows=6700 width=41) (actual time=0.013..9.147 rows=8 896 loops=1) Output: id, info, crt_date Buffers: shared hit=8909 Planning time: 0.253 ms Execution time: 9.911 ms (5 rows) 當然這么做有很大的弊端,因為如果熱值比較多,我們要為各種熱值相關的查詢條件創建很多的索引。 2. 因為一條記錄一天最多更新一次,所以完全可以使用應用層緩存,或者pgmemcache這樣的緩存插件,降低數據庫的負擔。 3. 使用游標,我們注意到用戶使用了分頁顯示,但是對於用戶來說,可能只會看第一頁或前幾頁的內容,所以每次都全部取到程序端是沒有必要的,用游標會更好。(注意不要使用order by limit x offset x這種方式分頁,會冗余掃描多次,請使用cursor,但是記得用完關閉。)詳見驅動API,如pg-jdbc。 壓力測試: 測量類似分頁,我這里只取第一頁的內容(使用熱值partial index)。 注意這種用法不是游標的用法。只是方便這里測試的。 vi test.sql select * from test where info @> '{1}'::int[] order by crt_date desc limit 10; 性能非常可觀: pg95@db-172-16-3-150-> pgbench -M prepared -n -r -f ./test.sql -P 1 -c 16 -j 16 -T 30 progress: 1.0 s, 72844.1 tps, lat 0.213 ms stddev 0.119 progress: 2.0 s, 73691.9 tps, lat 0.215 ms stddev 0.019 progress: 3.0 s, 73603.7 tps, lat 0.216 ms stddev 0.018 progress: 4.0 s, 73501.3 tps, lat 0.216 ms stddev 0.063 progress: 5.0 s, 73433.2 tps, lat 0.216 ms stddev 0.049 progress: 6.0 s, 73645.1 tps, lat 0.216 ms stddev 0.023 progress: 7.0 s, 73551.0 tps, lat 0.216 ms stddev 0.060 progress: 8.0 s, 73640.9 tps, lat 0.216 ms stddev 0.018 progress: 9.0 s, 73650.8 tps, lat 0.216 ms stddev 0.027 progress: 10.0 s, 73753.5 tps, lat 0.215 ms stddev 0.068 對比一次取完所有數據的性能: pg95@db-172-16-3-150-> vi test.sql select * from test where info @> '{1}'::int[] order by crt_date desc; pg95@db-172-16-3-150-> pgbench -M prepared -n -r -f ./test.sql -P 1 -c 16 -j 16 -T 30 progress: 1.0 s, 219.9 tps, lat 68.165 ms stddev 7.355 progress: 2.0 s, 233.8 tps, lat 67.849 ms stddev 15.181 progress: 3.0 s, 238.4 tps, lat 68.023 ms stddev 10.556 progress: 4.0 s, 233.9 tps, lat 68.030 ms stddev 4.459 progress: 5.0 s, 233.6 tps, lat 68.019 ms stddev 4.131 progress: 6.0 s, 235.5 tps, lat 67.472 ms stddev 3.204 progress: 7.0 s, 237.7 tps, lat 67.627 ms stddev 3.257 progress: 8.0 s, 233.5 tps, lat 67.779 ms stddev 4.815 progress: 9.0 s, 238.7 tps, lat 67.723 ms stddev 7.603 progress: 10.0 s, 232.0 tps, lat 68.098 ms stddev 13.948 [參考] 1. http://www.postgresql.org/docs/9.4/static/functions-array.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM