時序數據合并場景加速分析和實現 - 復合索引,窗口分組查詢加速,變態遞歸加速...

時序數據合并場景加速分析和實現 - 復合索引,窗口分組查詢加速,變態遞歸加速

作者

digoal

日期

2016-11-28

標簽

PostgreSQL , 數據合并 , 時序數據 , 復合索引 , 窗口查詢


背景

在很多場景中,都會有數據合并的需求。

例如記錄了表的變更明細(insert,update,delete),需要合并明細,從明細中快速取到每個PK的最新值。

又比如有很多傳感器,不斷的在上報數據,要快速的取出每個傳感器的最新狀態。

對于這種需求,可以使用窗口查詢,但是如何加速,如何快速的取出批量數據呢?

這個是有優化的門道的。

傳感器例子

假設傳感器數據不斷的上報,用戶需要查詢當前最新的,每個傳感器上報的值。

創建測試表如下,

create unlogged table sort_test(id serial8 primary key,  -- 主鍵c2 int,  -- 傳感器IDc3 int  -- 傳感器值
);  寫入1000萬傳感器測試數據
postgres=# insert into sort_test (c2,c3) select random()*100000, random()*100 from generate_series(1,10000000);
INSERT 0 10000000

查詢語句如下

postgres=# explain (analyze,verbose,timing,costs,buffers) select id,c2,c3 from (select id,c2,c3,row_number() over(partition by c2 order by id desc) rn from sort_test) t where rn=1;QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------Subquery Scan on t  (cost=10001512045.83..10001837045.83 rows=50000 width=16) (actual time=23865.363..44033.984 rows=100001 loops=1)Output: t.id, t.c2, t.c3Filter: (t.rn = 1)Rows Removed by Filter: 9899999Buffers: shared hit=54055, temp read=93801 written=93801->  WindowAgg  (cost=10001512045.83..10001712045.83 rows=10000000 width=24) (actual time=23865.351..41708.460 rows=10000000 loops=1)Output: sort_test.id, sort_test.c2, sort_test.c3, row_number() OVER (?)Buffers: shared hit=54055, temp read=93801 written=93801->  Sort  (cost=10001512045.83..10001537045.83 rows=10000000 width=16) (actual time=23865.335..31540.089 rows=10000000 loops=1)Output: sort_test.id, sort_test.c2, sort_test.c3Sort Key: sort_test.c2, sort_test.id DESCSort Method: external merge  Disk: 254208kBBuffers: shared hit=54055, temp read=93801 written=93801->  Seq Scan on public.sort_test  (cost=10000000000.00..10000154055.00 rows=10000000 width=16) (actual time=0.021..1829.135 rows=10000000 loops=1)Output: sort_test.id, sort_test.c2, sort_test.c3Buffers: shared hit=54055Planning time: 0.194 msExecution time: 44110.560 ms
(18 rows)

優化手段,新增復合索引,避免SORT,注意,id需要desc

postgres=# create index sort_test_1 on sort_test(c2,id desc); 
CREATE INDEX

優化后的SQL性能

postgres=# explain (analyze,verbose,timing,costs,buffers) select id,c2,c3 from (select id,c2,c3,row_number() over(partition by c2 order by id desc) rn from sort_test) t where rn=1;QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------Subquery Scan on t  (cost=0.43..542565.80 rows=50000 width=16) (actual time=0.048..33844.843 rows=100001 loops=1)Output: t.id, t.c2, t.c3Filter: (t.rn = 1)Rows Removed by Filter: 9899999Buffers: shared hit=10029020 read=1->  WindowAgg  (cost=0.43..417564.59 rows=10000097 width=24) (actual time=0.042..30490.662 rows=10000000 loops=1)Output: sort_test.id, sort_test.c2, sort_test.c3, row_number() OVER (?)Buffers: shared hit=10029020 read=1->  Index Scan using sort_test_1 on public.sort_test  (cost=0.43..242562.89 rows=10000097 width=16) (actual time=0.030..18347.482 rows=10000000 loops=1)Output: sort_test.id, sort_test.c2, sort_test.c3Buffers: shared hit=10029020 read=1Planning time: 0.216 msExecution time: 33865.321 ms
(13 rows)

如果被取出的數據需要后續的處理,可以使用游標,分批獲取,因為不需要顯示sort,所以分批獲取速度很快,從而加快整個的處理速度。

\timing
begin;
declare c1 cursor for select id,c2,c3 from (select id,c2,c3,row_number() over(partition by c2 order by id desc) rn from sort_test) t where rn=1;
postgres=# fetch 100 from c1;id    | c2 | c3  
---------+----+-----9962439 |  0 |  939711199 |  1 |  529987709 |  2 |  659995611 |  3 |  349998766 |  4 |  129926693 |  5 |  81....9905064 | 98 |  449991592 | 99 |  99
(100 rows)
Time: 31.408 ms  -- 很快就返回

優化前,需要顯示SORT,所以使用游標并不能加速,拿到第一條記錄是在SORT后的。

drop index sort_test_1;begin;
declare c1 cursor for select id,c2,c3 from (select id,c2,c3,row_number() over(partition by c2 order by id desc) rn from sort_test) t where rn=1;postgres=# fetch 100 from c1;
....
Time: 22524.783 ms  -- sort結束后才開始返回,很慢

增量合并數據同步例子

類似Oracle的物化視圖,apply時,對于同一條記錄的update并不需要每次update的中間過程都需要執行,只需要執行最后一次的。

因此,也可以利用類似的操作手段,分組取最后一條,

create extension hstore;create unlogged table sort_test1(id serial8 primary key,  -- 主鍵c2 int,  -- 目標表PKc3 text,  -- insert or update or deletec4 hstore -- row
); create index idx_sort_test1_1 on sort_test1(c2,id desc);select c2,c3,c4 from (select c2,c3,c4,row_number() over(partition by c2 order by id desc) rn from sort_test1) t where rn=1;postgres=# explain select c2,c3,c4 from (select c2,c3,c4,row_number() over(partition by c2 order by id desc) rn from sort_test1) t where rn=1;QUERY PLAN                                             
---------------------------------------------------------------------------------------------------Subquery Scan on t  (cost=0.15..46.25 rows=4 width=68)Filter: (t.rn = 1)->  WindowAgg  (cost=0.15..36.50 rows=780 width=84)->  Index Scan using idx_sort_test1_1 on sort_test1  (cost=0.15..22.85 rows=780 width=76)
(4 rows)

稀疏列的變態優化方法

我們看到前面的優化手段,其實只是消除了SORT,并沒有消除掃描的BLOCK數。

如果分組很少時,即稀疏列,還有一種更變態的優化方法,遞歸查詢。

優化方法與這篇文檔類似,

《distinct xx和count(distinct xx)的變態遞歸優化方法》

例子

create type r as (c2 int, c3 int);postgres=# explain (analyze,verbose,timing,costs,buffers) with recursive skip as (  (  select (c2,c3)::r as r from sort_test where id in (select id from sort_test where c2 is not null order by c2,id desc limit 1) )  union all  (  select (select (c2,c3)::r as r from sort_test where id in (select id from sort_test t where t.c2>(s.r).c2 and t.c2 is not null order by c2,id desc limit 1) ) from skip s where (s.r).c2 is not null)    -- 這里的where (s.r).c2 is not null 一定要加, 否則就死循環了. 
)   
select (t.r).c2, (t.r).c3 from skip t where t.* is not null; QUERY PLAN                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------CTE Scan on skip t  (cost=302.97..304.99 rows=100 width=8) (actual time=0.077..4184.770 rows=100001 loops=1)Output: (t.r).c2, (t.r).c3Filter: (t.* IS NOT NULL)Rows Removed by Filter: 1Buffers: shared hit=800947, temp written=476CTE skip->  Recursive Union  (cost=0.91..302.97 rows=101 width=32) (actual time=0.066..3970.580 rows=100002 loops=1)Buffers: shared hit=800947->  Nested Loop  (cost=0.91..2.95 rows=1 width=32) (actual time=0.064..0.066 rows=1 loops=1)Output: ROW(sort_test_1.c2, sort_test_1.c3)::rBuffers: shared hit=8->  HashAggregate  (cost=0.47..0.48 rows=1 width=8) (actual time=0.044..0.044 rows=1 loops=1)Output: sort_test_2.idGroup Key: sort_test_2.idBuffers: shared hit=4->  Limit  (cost=0.43..0.46 rows=1 width=12) (actual time=0.036..0.036 rows=1 loops=1)Output: sort_test_2.id, sort_test_2.c2Buffers: shared hit=4->  Index Only Scan using sort_test_1 on public.sort_test sort_test_2  (cost=0.43..267561.43 rows=10000000 width=12) (actual time=0.034..0.034 rows=1 loops=1)Output: sort_test_2.id, sort_test_2.c2Index Cond: (sort_test_2.c2 IS NOT NULL)Heap Fetches: 1Buffers: shared hit=4->  Index Scan using sort_test_pkey on public.sort_test sort_test_1  (cost=0.43..2.45 rows=1 width=16) (actual time=0.011..0.012 rows=1 loops=1)Output: sort_test_1.id, sort_test_1.c2, sort_test_1.c3Index Cond: (sort_test_1.id = sort_test_2.id)Buffers: shared hit=4->  WorkTable Scan on skip s  (cost=0.00..29.80 rows=10 width=32) (actual time=0.037..0.038 rows=1 loops=100002)Output: (SubPlan 1)Filter: ((s.r).c2 IS NOT NULL)Rows Removed by Filter: 0Buffers: shared hit=800939SubPlan 1->  Nested Loop  (cost=0.92..2.96 rows=1 width=32) (actual time=0.034..0.035 rows=1 loops=100001)Output: ROW(sort_test.c2, sort_test.c3)::rBuffers: shared hit=800939->  HashAggregate  (cost=0.49..0.50 rows=1 width=8) (actual time=0.023..0.023 rows=1 loops=100001)Output: t_1.idGroup Key: t_1.idBuffers: shared hit=400401->  Limit  (cost=0.43..0.48 rows=1 width=12) (actual time=0.021..0.021 rows=1 loops=100001)Output: t_1.id, t_1.c2Buffers: shared hit=400401->  Index Only Scan using sort_test_1 on public.sort_test t_1  (cost=0.43..133557.76 rows=3333333 width=12) (actual time=0.019..0.019 rows=1 loops=100001)Output: t_1.id, t_1.c2Index Cond: ((t_1.c2 > (s.r).c2) AND (t_1.c2 IS NOT NULL))Heap Fetches: 100000Buffers: shared hit=400401->  Index Scan using sort_test_pkey on public.sort_test  (cost=0.43..2.45 rows=1 width=16) (actual time=0.006..0.007 rows=1 loops=100000)Output: sort_test.id, sort_test.c2, sort_test.c3Index Cond: (sort_test.id = t_1.id)Buffers: shared hit=400538Planning time: 0.970 msExecution time: 4209.026 ms
(54 rows)

依舊支持快速的FETCH

postgres=# begin;
BEGIN
Time: 0.079 ms
postgres=# declare cur cursor for with recursive skip as (  (  select (c2,c3)::r as r from sort_test where id in (select id from sort_test where c2 is not null order by c2,id desc limit 1) )  union all  (  select (select (c2,c3)::r as r from sort_test where id in (select id from sort_test t where t.c2>(s.r).c2 and t.c2 is not null order by c2,id desc limit 1) ) from skip s where (s.r).c2 is not null)    -- 這里的where (s.r).c2 is not null 一定要加, 否則就死循環了. 
)   
select (t.r).c2, (t.r).c3 from skip t where t.* is not null; 
DECLARE CURSOR
Time: 1.240 ms
postgres=# fetch 100 from cur;r     
----------(0,93)(1,52)(2,65)
.....(97,78)(98,44)(99,99)
(100 rows)Time: 4.314 ms

使用變態的遞歸優化,性能提升了10倍,僅僅花了4秒,完成了1000萬記錄的篩選。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/457492.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/457492.shtml
英文地址,請注明出處:http://en.pswp.cn/news/457492.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

navicat for mysql 數據庫備份與還原

一, 首先設置, 備份保存路徑 工具 -> 選項 點開 其他 -> 日志文件保存路徑 二. 開始備份 備份分兩種, 一種是以sql保存, 一種是保存為備份 SQL保存 右鍵點擊你要備份的數據庫, -> 轉儲SQL文件 選擇位置和文件名 開始轉儲 導入 建議 刪除所有表 或 重新建數據庫 同導出…

DES的原理及python實現

DES加密算法原理及實現 DES是一種對稱加密算法【即發送者與接收者持有相同的密鑰】,它的基本原理是將要加密的數據劃分為n個64位的塊,然后使用一個56位的密鑰逐個加密每一個64位的塊,得到n個64位的密文塊,最后將密文塊拼接起來得…

python按身高體重排隊_LeetCode-python 406.根據身高重建隊列

題目鏈接難度:中等 類型: 數組假設有打亂順序的一群人站成一個隊列。 每個人由一個整數對(h, k)表示,其中h是這個人的身高,k是排在這個人前面且身高大于或等于h的人數。 編寫一個算法來重建這個隊列。注意:總人數…

遠程連接mysql數據庫,1130問題

遠程或使用非127.0.0.1和localhost地址連接時,出現代號為1130問題, ERROR 1130: Host 192.168.2.159 is not allowed to connect to this MySQL server 猜想這是沒有授權,將mysql數據庫中user表中host列的localhost改為%,重新啟動…

華為手機充滿有提醒嗎_2020手機充電速度排名:最快21分鐘充滿,華為第15名

5G手機扎堆出現,中國5G基站數量也是不斷增多,中國移動曾經表態,2020年底將會在全國地級市覆蓋5G網絡,全民5G時代終于到來!從目前國內手機出貨量數據來看,5G手機占比已經達到了六成以上,國產5G手…

關于移動手機端富文本編輯器qeditor圖片上傳改造

日前項目需要在移動端增加富文本編輯,上網找了下,大多數都是針對pc版的,不太兼容手機,當然由于手機屏幕小等原因也限制富文本編輯器的眾多強大功能,所以要找的編輯器功能必須是精簡的。 找了好久,發現qedit…

【python】生成器

生成器 直接總結 創建生成器的方法 生成器表達式:(i for i in [1, 2])yield: 函數中出現yield這個函數就是生成器,函數(生成器)執行到yield時會返回yield后面的值,并暫停,知道下次被喚醒后會從暫停處接著…

python redis 性能測試臺_Redis性能測試

Redis 性能測試Redis 性能測試是通過同時執行多個命令實現的。Redis性能測試主要是通過src文件夾下的redis-benchmark來實現(Linux系統下)語法redis 性能測試的基本命令如下:redis-benchmark [option] [option value]實例以下實例同時執行 10000 個請求來檢測性能&a…

Java IO 系統

Java IO系統 File類 用來處理文件目錄,既可以代表一個特定文件的名稱,也可以代表一組文件的名稱,如果代表的是一個文件組,可以調用File.list()方法返回一個字符數組。 list()不傳遞任何參數時返回該目錄下所有文件或文件名的字…

Linux Crontab 任務管理工具命令以及示例

Crontab 是 Linux 平臺下的一款用于循環執行例行任務的工具,Linux 系統由 cron (crond) 這個系統服務來控制任務 , Linux系統本來就有很多的計劃任務需要啟動 , 所以這個系統服務是默認開機啟動的 。 Linux 為使用者提供的計劃任務的命令就是 Crontab Crontab 是 Linux 下用來周…

Linux 網絡編程詳解一(IP套接字結構體、網絡字節序,地址轉換函數)

IPv4套接字地址結構 struct sockaddr_in {uint8_t sinlen;(4個字節)sa_family_t sin_family;(4個字節)in_port_t sin_port;(2個字節)struct in_addr sin_addr;(4個字節)char sin_zer…

地籍cad的lisp程序大集合_AutoCAD-LISP程序100例

{:soso_e179:}AutoCAD-LISP程序100例.JPG (143.82 KB, 下載次數: 28)2011-10-18 14:42 上傳有說明很好!頂如果您使用 AutoCAD,下面的內容對您一定有幫助。在某些方面能大大提高您的工作效率。下面的程序均以源程序方式給出,您可以使用、參考、修改它。bg…

javascript中數組的22種方法

前面的話數組總共有22種方法,本文將其分為對象繼承方法、數組轉換方法、棧和隊列方法、數組排序方法、數組拼接方法、創建子數組方法、數組刪改方法、數組位置方法、數組歸并方法和數組迭代方法共10類來進行詳細介紹對象繼承方法數組是一種特殊的對象,繼…

javascript/jquery高度寬度詳情解說分析

為什么80%的碼農都做不了架構師?>>> 一、window對象表示瀏覽器中打開的窗口 二、window對象可以省略 一、document對象是window對象的一部分 二、瀏覽器的HTML文檔成為Document對象 window.location和document.location window對象的location屬性引用的…

農用地包括哪些地類_土地地類一覽表

一級類二級類三級類含義編號三大類名稱編號名稱編號名稱1農用地指直接用于農業生產的土地,包括耕地,園地,林地,牧草地及其他的農業用地11耕地指種植農作物、土地,包括熟地、新開發復墾整理地,休閑地、輪歇地…

紅黑樹插入時的自平衡

紅黑樹插入時的自平衡 紅黑樹實質上是一棵自平衡的二叉查找樹,引入帶顏色的節點也是為了方便在進行插入或刪除操作時,如果破壞了二叉查找樹的平衡性能通過一系列變換保持平衡。 紅黑樹的性質 每個節點要么是紅色,要么是黑色根節點必須是黑…

說一下自己對于 Linux 哲學的理解

查閱了一些資料,官方的哲學思想貌似是: 一切皆文件由眾多單一目的的小程序,一個程序只實現一個功能,多個程序組合完成復雜任務文本文件保存配置信息盡量避免與用戶交互什么,你問我的理解?哲學思想&#xff…

UWP學習記錄

微軟{X:Bind}、{Binding}資料網站 &#xff1a; https://msdn.microsoft.com/windows/uwp/xaml-platform/x-bind-markup-extension在View的ItemTemplate中綁定ViewModel的方法&#xff1a;1 <ItemsControl Name"XX" ItemsSource"{x:Bind VM.XXModels,ModeOne…

dw1000信標碼_DW1000方案工牌型UWB標簽,助力10厘米高精度室內定位!

DW1000方案工牌型UWB標簽&#xff0c;助力10厘米高精度室內定位&#xff01;發布日期&#xff1a;2019-04-01 瀏覽次數&#xff1a;244次微能信息(95power)推出一款工牌型UWB標簽VDU1510 &#xff0c;廣泛應用于超寬帶UWB定位系統&#xff0c;最高可實現10cm高精度人員定位。工…

【Java】HashMap源碼(1.7)

Life is not a ridiculous number of life, the meaning of life lies in life itself HashMap源碼 散列集 數組和鏈表可以保持元素插入的順序&#xff0c;對數組來說&#xff0c;他的優點是擁有連續的存儲空間&#xff0c;因此可以使用元素下標快速訪問&#xff0c;但缺點在…