閱讀48 返回首頁    go 阿裏雲 go 技術社區[雲棲]


PgSQL · 應用案例 · HDB for PG特性(數據排盤與任意列高效率過濾)

背景

數據也有生辰八字,你信嗎?列與列之間,行與行之間,元素與元素之間如何相生相克?查詢慢?不要信什麼這都是上天注定的,如何給數據改運?看完本文,你也可以做到。

pic

一份天賦,九份努力。緣分天注定。命由天定。又有說我命由我不由天的。看樣子中國古人對先天注定的東西研究還挺透徹,看的還挺開,但是也有通過後天努力,或者後天改運等等手段來彌補先天不足的。

pic

實際上在準備寫本文時,我發現數據庫的數據編排,數據存放和中國的命理相關的傳統文化還很相似,也存在先天因素和後天補救的說法。

怎麼回事呢?且聽我細細道來。

為了加速數據的檢索效率,我們通常需要對數據創建索引,提高數據定位的精準性。例如查詢某人某個月的通話流水數據,沒有索引的話,我們需要搜索所有的數據,逐條匹配。通過索引,可以直接定位到需要查詢的記錄。

特別是在存儲和計算分離時,如果搜索量越大,網絡中傳輸的數據量就越大。瓶頸很明顯。

另外,在OLAP領域,需要對大量的數據進行處理,如果都建索引,索引引入的開銷還是蠻大的。

那麼有沒有其他方法,不建索引降低掃描量呢?

存儲層統計和過濾下推

相信大家一定已經想到了,統計信息,沒錯我們可以對存儲的數據,按塊進行數據統計,例如每個塊內的數據範圍。

有幾個非常常見的技術實現:

1、PostgreSQL BRIN索引。

《PostgreSQL 物聯網黑科技 - 瘦身幾百倍的索引(BRIN index)》

https://www.postgresql.org/docs/10/static/brin-intro.html

PostgreSQL brin索引就是塊級索引,記錄的是每個塊、或者每一批連續的塊的統計信息。

在按這個列搜索時,通過元數據,過濾不相幹的塊。

2、cstore_fdw列存儲插件。實際上它也是按BATCH編排的列存儲,每個BATCH的元數據(最大值、最小值)可以用於掃描時的過濾。

https://github.com/citusdata/cstore_fdw

Skip indexes: Stores min/max statistics for row groups, and uses them to skip over unrelated rows.

Using Skip Indexes

cstore_fdw partitions each column into multiple blocks. Skip indexes store minimum and maximum values for each of these blocks. While scanning the table, if min/max values of the block contradict the WHERE clause, then the block is completely skipped. This way, the query processes less data and hence finishes faster.

To use skip indexes more efficiently, you should load the data after sorting it on a column that is commonly used in the WHERE clause. This ensures that there is a minimum overlap between blocks and the chance of them being skipped is higher.

In practice, the data generally has an inherent dimension (for example a time field) on which it is naturally sorted. Usually, the queries also have a filter clause on that column (for example you want to query only the last week’s data), and hence you don’t need to sort the data in such cases.

在按這個列搜索時,通過元數據,過濾不相幹的塊。

例子

某個300GB的外部表,采樣skip index掃描,加速掃描。  
耗時103毫秒。      
      
explain (analyze,verbose,timing,costs,buffers) select c400,sum(c2) from ft_tbl1 where c400=1 group by c400;      
      
         Filter: (ft_tbl1.c400 = 1)      
         Rows Removed by Filter: 89996        
         CStore File: /data01/digoal/pg_root1921/cstore_fdw/13146/41038      
         CStore File Size: 325166400400      
         Buffers: shared hit=8004      
 Planning time: 52.524 ms      
 Execution time: 103.555 ms      
(13 rows)      
      
不使用where c400=1,  
耗時89秒      
explain (analyze,verbose,timing,costs,buffers) select c400,sum(c2) from ft_tbl1  group by c400;      
      
         CStore File: /data01/digoal/pg_root1921/cstore_fdw/13146/41038      
         CStore File Size: 325166400400      
         Buffers: shared hit=8004      
 Planning time: 52.691 ms      
 Execution time: 89428.721 ms      

需要提一下,目前cstore_fdw這個插件沒有支持並行計算,而實際上PostgreSQL的fdw接口已經支持了並行計算,cstore_fdw隻需要改造一下,即可支持並行計算。

如下

https://www.postgresql.org/docs/10/static/fdw-callbacks.html

過濾效率與線性相關性

注意,由於數據存儲的關係,並不是所有列的統計信息過濾性都很好。舉個例子:

某列的寫入很隨機,導致值的分布很隨機,那麼在一個數據塊裏麵包含的數據範圍可能比較大,這種列的存儲元信息過濾性就很差。

create table a(id int, c1 int);      
insert into a select generate_series(1,1000000), random()*1000000;      

數據的分布如下

postgres=# select substring(ctid::text, '(\d+),')::int8 blkid, min(c1) min_c1, max(c1) max_c1, min(id) min_id, max(id) max_id from a group by 1 order by 1;      
 blkid | min_c1 | max_c1 | min_id | max_id        
-------+--------+--------+--------+---------      
     0 |   2697 | 998322 |      1 |     909      
     1 |   1065 | 998817 |    910 |    1818      
     2 |    250 | 998025 |   1819 |    2727      
     3 |     62 | 997316 |   2728 |    3636      
     4 |   1556 | 998640 |   3637 |    4545      
     5 |    999 | 999536 |   4546 |    5454      
     6 |   1385 | 999196 |   5455 |    6363      
     7 |   1809 | 999042 |   6364 |    7272      
     8 |   3044 | 999606 |   7273 |    8181      
     9 |   1719 | 999186 |   8182 |    9090      
    10 |    618 | 997031 |   9091 |    9999      
    11 |     80 | 997581 |  10000 |   10908      
    12 |    781 | 997710 |  10909 |   11817      
    13 |   1539 | 998857 |  11818 |   12726      
    14 |   2097 | 999932 |  12727 |   13635      
    15 |    114 | 999913 |  13636 |   14544      
    16 |    136 | 999746 |  14545 |   15453      
    17 |   2047 | 997439 |  15454 |   16362      
    18 |   1955 | 996937 |  16363 |   17271      
    19 |   1487 | 999705 |  17272 |   18180      
    20 |     97 | 999549 |  18181 |   19089      
    21 |    375 | 999161 |  19090 |   19998      
    22 |    645 | 994457 |  19999 |   20907      
    23 |   4468 | 998612 |  20908 |   21816      
    24 |    865 | 996342 |  21817 |   22725      
    25 |    402 | 998151 |  22726 |   23634      
    26 |    429 | 998823 |  23635 |   24543      
    27 |   1305 | 999521 |  24544 |   25452      
    28 |    974 | 998874 |  25453 |   26361      
    29 |   1056 | 999271 |  26362 |   27270      
。。。。。。      

對於ID列,分布非常清晰(線性相關性好),存儲元數據的過濾性好。而C1列,分布非常散,存儲元數據的過濾性差。

例如我要查id=10000的數據,直接查11號數據塊,跳過其他數據塊的掃描。

而如果我要查c1=10000的數據,那麼要查很多個數據塊,因為能跳過的數據塊很少。

如何提升每一列的過濾性 - 存儲編排

對於單列來說,提升過濾性的方法非常簡單,按順序存儲即可。

例如前麵的測試表,我們要提高C1的過濾性,按C1重排一下即可實現。

重排後,C1列與物理存儲(行號)的相關性會變成1或-1,即線性相關,因此過濾性就特別好。

postgres=# create temp table tmp_a (like a);      
CREATE TABLE      
postgres=# insert into tmp_a select * from a order by c1;      
INSERT 0 1000000      
postgres=# truncate a;      
TRUNCATE TABLE      
postgres=# insert into a select * from tmp_a;      
INSERT 0 1000000      
postgres=# end;      
COMMIT      
postgres=# select substring(ctid::text, '(\d+),')::int8 blkid, min(c1) min_c1, max(c1) max_c1, min(id) min_id, max(id) max_id from a group by 1 order by 1;      
 blkid | min_c1 | max_c1 | min_id | max_id        
-------+--------+--------+--------+---------      
     0 |      0 |    923 |   2462 |  999519      
     1 |    923 |   1846 |   1487 |  997619      
     2 |   1847 |   2739 |    710 |  999912      
     3 |   2741 |   3657 |   1930 |  999053      
     4 |   3658 |   4577 |   1635 |  999579      
     5 |   4577 |   5449 |    852 |  999335      
     6 |   5450 |   6410 |    737 |  998277      
     7 |   6414 |   7310 |   3262 |  999024      
     8 |   7310 |   8245 |    927 |  997907      
     9 |   8246 |   9146 |    441 |  999209      
    10 |   9146 |  10015 |    617 |  999828      
    11 |  10016 |  10920 |   1226 |  998264      
    12 |  10923 |  11859 |   1512 |  997404      
    13 |  11862 |  12846 |    151 |  998737      
    14 |  12847 |  13737 |   1007 |  999250      
。。。。。。      
      
c1列和物理存儲(行號)的線性相關性      
postgres=# select correlation from pg_stats where tablename='a' and attname='c1';      
 correlation       
-------------      
           1      
(1 row)      

糟糕的是,這麼編排後,ID這個字段的過濾性就變差了。

這是為什麼呢?

全局/全表 兩列相對線性相關性

實際上是ID和C1列的相關性,它控製了按C1排序後ID列變離散的問題。

ID和C1的相關性如何呢?

postgres=# select corr(c1,id) from (select row_number() over(order by c1) c1, row_number() over(order by id) id from a) t;      
         corr                
-----------------------      
 -0.000695987373950136      
(1 row)      

c1和id的全局(全表)相關性極差,導致了這個問題。

(可以理解為這兩個字段的八字不合)

pic

局部/部分記錄 兩列相對線性相關性

如果全表按C1或ID排序,那麼另一列的離散度就會變得很高。

但是,某些情況下,可能存在這樣的情況,某些記錄A和B字段的相關性很好,而其他記錄他們的相關性不好。

例子

在之前的記錄基礎上,再插入一批記錄。

postgres=# insert into a select id, id*2 from generate_series(1,100000) t(id);      
INSERT 0 100000      

這部分數據id, c1字段的相關性為1。(局部相關性)

postgres=# select ctid from a offset 1000000 limit 1;      
    ctid          
------------      
 (1113,877)      
(1 row)      
      
postgres=# select corr(c1,id) from (select row_number() over(order by c1) c1, row_number() over(order by id) id from a where ctid >'(1113,877)') t;      
 corr       
------      
    1      
(1 row)      

全局相關性一下也提升了不少

postgres=# select corr(c1,id) from (select row_number() over(order by c1) c1, row_number() over(order by id) id from a) t;      
       corr              
-------------------      
 0.182542794451908      
(1 row)      

局部按需改命法

數據散落存儲,帶來的問題:即使訪問少量數據,也會造成大量的IO讀取,原理如下:

《索引順序掃描引發的堆掃描IO放大背後的統計學原理與解決辦法》

數據存儲是上天注定的(寫入時就決定了),但是我們可以按需改命,例如有個業務是運營商的通話流水,查詢需求通常是按某個手機號碼查詢一個月的流水。而實際上數據是產生時即時寫入數據庫的,所以存放散亂。查詢時耗費大量IO。

例子

用戶通話數據即時寫入,用戶數據呈現布朗分布。

create table phone_list(phone_from char(11), phone_to char(11), crt_time timestamp, duration interval);  
create index idx_phone_list on phone_list(phone_from, crt_time);  
  
insert into phone_list   
select   
  lpad((random()*1000)::int8::text, 11, '1'),   
  lpad((random()*1000)::int8::text, 11, '1'),   
  now()+(id||' second')::interval,  
  ((random()*1000)::int||' second')::interval  
from generate_series(1,10000000) t(id);  
  
postgres=# select * from phone_list limit 10;  
 phone_from  |  phone_to   |          crt_time          | duration   
-------------+-------------+----------------------------+----------  
 14588832692 | 11739044013 | 2017-08-11 10:17:04.752157 | 00:03:25  
 15612918106 | 11808103578 | 2017-08-11 10:17:05.752157 | 00:11:33  
 14215811756 | 15983559210 | 2017-08-11 10:17:06.752157 | 00:08:05  
 13735246090 | 15398474974 | 2017-08-11 10:17:07.752157 | 00:13:18  
 19445131039 | 17771201972 | 2017-08-11 10:17:08.752157 | 00:00:10  
 11636458384 | 16356298444 | 2017-08-11 10:17:09.752157 | 00:06:30  
 15771059012 | 14717265381 | 2017-08-11 10:17:10.752157 | 00:13:45  
 19361008150 | 14468133189 | 2017-08-11 10:17:11.752157 | 00:05:58  
 13424293799 | 16589177297 | 2017-08-11 10:17:12.752157 | 00:16:29  
 12243665890 | 13538149386 | 2017-08-11 10:17:13.752157 | 00:16:03  
(10 rows)  

查詢效率低下,按手機查詢通話記錄,返回29937條記錄需要26毫秒。

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from phone_list where phone_from='11111111111' order by crt_time;  
                                                                   QUERY PLAN                                                                     
------------------------------------------------------------------------------------------------------------------------------------------------  
 Index Scan using idx_phone_list on public.phone_list  (cost=0.56..31443.03 rows=36667 width=48) (actual time=0.016..24.348 rows=29937 loops=1)  
   Output: phone_from, phone_to, crt_time, duration  
   Index Cond: (phone_list.phone_from = '11111111111'::bpchar)  
   Buffers: shared hit=25843  
 Planning time: 0.082 ms  
 Execution time: 25.821 ms  
(6 rows)  

改命方法,局部按需調整。

需求是高效的按手機和月查詢通話詳單,所以我們需要將用戶一個月的數據(通常是按月分區)進行重排即可。

分區表用法見:《PostgreSQL 10.0 preview 功能增強 - 內置分區表》

postgres=# cluster phone_list using idx_phone_list ;  

查詢效率驟然提升,改命成功。

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from phone_list where phone_from='11111111111' order by crt_time;  
                                                                  QUERY PLAN                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------  
 Index Scan using idx_phone_list on public.phone_list  (cost=0.56..31443.03 rows=36667 width=48) (actual time=0.012..4.590 rows=29937 loops=1)  
   Output: phone_from, phone_to, crt_time, duration  
   Index Cond: (phone_list.phone_from = '11111111111'::bpchar)  
   Buffers: shared hit=432  
 Planning time: 0.038 ms  
 Execution time: 5.968 ms  
(6 rows)  

你就是上帝之手,數據的命運掌握在你的手中。

如何提升每一列的過濾性 - 存儲編排

為了獲得最好的過濾性(每個列都能很好的過濾),采用全局排序滿足不了需求。

實際上需要局部排序,例如前麵的例子,前麵100萬行,按C1排序,後麵10萬行再按ID排序。

這樣的話有10萬記錄的ID的過濾性很好,有110萬記錄的C1的過濾性也很好。

但是數都是有命理的,就好像人的姓名也分為五格。

pic

通過後天的補救,可以改運。道理和數據編排一樣,數據重排,可以影響全局過濾性,局部過濾性,是不是很有意思呢?

根據你的查詢目標需求,重排數據,一起來改運吧。

複合排序 多列相對線性相關性

多列如何做到每列都具備良好的聚集性呢?

1、最土的方法,多列排序,但是效果其實並不一定好。為了達到更好的效果,需要調整列的順序,算法如下:

我記得以前寫過一篇這樣的文檔:

《一個簡單算法可以幫助物聯網,金融 用戶 節約98%的數據存儲成本 (PostgreSQL,Greenplum幫你做到)》

這裏講的實際上也是存儲編排的精髓,通過排列組合,計算每兩列的線性相關性,根據這個找出最佳的多列排序組合,從而提高整體相關性(提高壓縮比)。

同樣適用於本文提到的提高所有列的過濾性。

2、k-means算法,針對多列進行聚集計算,完成最佳的局部分布,這樣做就能達到每個列的過濾性都很讚了。

《K-Means 數據聚集算法》

精髓

1、局部、全局 兩列相對相關性。決定了按某列排序後,另一列的離散度。

2、編排的目的是,可以盡可能的讓更多的列有序的存儲,從而可以過濾最多的行。

3、全局相關性,決定了按某一列排序時,另一列的離散度。

4、局部相關性,決定了在某些記錄中,兩列的線性相關性。

5、按局部相關性編排,可以盡可能的讓更多的列有序的存儲,從而可以過濾最多的行。但是算法較複雜,需要算出什麼樣的行在一起,按什麼排序存放才能獲得最佳過濾性。

6、關於多列(或數組)的數據編排,方法1,通過排列組合,計算每兩列(元素)的線性相關性,根據這個找出最佳的多列排序組合,從而提高整體相關性(提高壓縮比)。

7、編排後,與存儲(行號)線性相關性差的列,如果選擇性較好(DISTINCT VALUE較多)時,並且業務有過濾數據的需求,建議還是需要建索引。

8、關於多列(或數組)的數據編排,方法2,通過kmean,算出數據歸為哪類,每類聚合存放,從而提高數據的局部聚集性,過濾性。這個方法是最優雅的。

9、經過編排,結合PG的BRIN索引,就可以實現任意列的高效過濾。

給數據改命的案例

1、多列改命

低級方法,《一個簡單算法可以幫助物聯網,金融 用戶 節約98%的數據存儲成本 (PostgreSQL,Greenplum幫你做到)》

高級方法,《K-Means 數據聚集算法》

pic

pic

pic

高級方法舉例

-- 寫入 1億 記錄
-- 天命,各列散落,五行紊亂,查詢效率低下。 
postgres=# create table tab(c1 int, c2 int, c3 int, c4 int, c5 int);
CREATE TABLE
postgres=# insert into tab select * from (select id,100000000-id,50000000-id, sqrt(id*2), sqrt(id) from generate_series(1,100000000) t(id)) t order by random();
INSERT 0 100000000
postgres=# select ctid,* from tab limit 10;
  ctid  |    c1    |    c2    |    c3     |  c4   |  c5  
--------+----------+----------+-----------+-------+------
 (0,1)  | 76120710 | 23879290 | -26120710 | 12339 | 8725
 (0,2)  | 98295593 |  1704407 | -48295593 | 14021 | 9914
 (0,3)  | 56133647 | 43866353 |  -6133647 | 10596 | 7492
 (0,4)  |   787639 | 99212361 |  49212361 |  1255 |  887
 (0,5)  | 89844299 | 10155701 | -39844299 | 13405 | 9479
 (0,6)  | 92618459 |  7381541 | -42618459 | 13610 | 9624
 (0,7)  | 93340303 |  6659697 | -43340303 | 13663 | 9661
 (0,8)  | 17164665 | 82835335 |  32835335 |  5859 | 4143
 (0,9)  |  2694394 | 97305606 |  47305606 |  2321 | 1641
 (0,10) | 41736122 | 58263878 |   8263878 |  9136 | 6460
(10 rows)
  
-- 改命,按K-MEAN聚集調整五行,采用BRIN索引實現任意列高效率過濾。
-- 讓每列在各個方向上保持一致,例如(a,b) (1,100)(2,101), (100,9)(105,15),如果歸為兩類,在過濾A字段時選擇性很好,過濾B字段時選擇性也很好。  
postgres=# create table tbl1(like tab);
CREATE TABLE

-- 由於數據按塊存儲,BRIN索引最小粒度為塊,所以我們的聚類數最多可以為表的塊數即可。例如636943個數據塊,那麼我們可以歸類為636943類。
-- 歸為超過636943類就沒有意義了,歸類為更少是可以的,例如BRIN索引每10個連續的數據塊存儲一個元信息,那麼我們可以選擇歸為63694類。  
postgres=# select relpages from pg_class where relname='tab';
 relpages 
----------
    636943
(1 row)
postgres=# insert into tbl1 select c1,c2,c3,c4,c5 from (select kmeans(array[c1,c2,c3,c4,c5],63694) over() km, * from tab) t order by km;
  
-- 創建任意列BRIN索引
create index idx_tab_1 on tab using brin(c1,c2,c3) with (pages_per_range=1);
create index idx_tbl1_1 on tbl1 using brin(c1,c2,c3) with (pages_per_range=1);

使用BRIN索引,在給數據改命後,任意列範圍搜索,提升高效,讚

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tab where c1 between 1 and 100000;
                                                             QUERY PLAN                                                              
-------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tab  (cost=4184.33..906532.40 rows=83439 width=20) (actual time=165.626..1582.402 rows=100000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tab.c1 >= 1) AND (tab.c1 <= 100000))
   Rows Removed by Index Recheck: 14427159
   Heap Blocks: lossy=92530
   Buffers: shared hit=96745
   ->  Bitmap Index Scan on idx_tab_1  (cost=0.00..4163.47 rows=17693671 width=0) (actual time=165.307..165.307 rows=925300 loops=1)
         Index Cond: ((tab.c1 >= 1) AND (tab.c1 <= 100000))
         Buffers: shared hit=4215
 Planning time: 0.088 ms
 Execution time: 1588.852 ms
(11 rows)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl1 where c1 between 1 and 100000;
                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tbl1  (cost=4159.34..111242.78 rows=95550 width=20) (actual time=157.084..169.314 rows=100000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tbl1.c1 >= 1) AND (tbl1.c1 <= 100000))
   Rows Removed by Index Recheck: 9
   Heap Blocks: lossy=637
   Buffers: shared hit=4852
   ->  Bitmap Index Scan on idx_tbl1_1  (cost=0.00..4135.45 rows=95613 width=0) (actual time=157.074..157.074 rows=6370 loops=1)
         Index Cond: ((tbl1.c1 >= 1) AND (tbl1.c1 <= 100000))
         Buffers: shared hit=4215
 Planning time: 0.083 ms
 Execution time: 174.069 ms
(11 rows)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tab where c2 between 1 and 100000;
                                                             QUERY PLAN                                                              
-------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tab  (cost=4183.50..902041.63 rows=82011 width=20) (actual time=165.901..1636.587 rows=100000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tab.c2 >= 1) AND (tab.c2 <= 100000))
   Rows Removed by Index Recheck: 14446835
   Heap Blocks: lossy=92655
   Buffers: shared hit=96870
   ->  Bitmap Index Scan on idx_tab_1  (cost=0.00..4163.00 rows=17394342 width=0) (actual time=165.574..165.574 rows=926550 loops=1)
         Index Cond: ((tab.c2 >= 1) AND (tab.c2 <= 100000))
         Buffers: shared hit=4215
 Planning time: 0.087 ms
 Execution time: 1643.089 ms
(11 rows)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl1 where c2 between 1 and 100000;
                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tbl1  (cost=4156.97..101777.70 rows=86127 width=20) (actual time=157.245..169.934 rows=100000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tbl1.c2 >= 1) AND (tbl1.c2 <= 100000))
   Rows Removed by Index Recheck: 115
   Heap Blocks: lossy=638
   Buffers: shared hit=4853
   ->  Bitmap Index Scan on idx_tbl1_1  (cost=0.00..4135.44 rows=86193 width=0) (actual time=157.227..157.227 rows=6380 loops=1)
         Index Cond: ((tbl1.c2 >= 1) AND (tbl1.c2 <= 100000))
         Buffers: shared hit=4215
 Planning time: 0.084 ms
 Execution time: 174.692 ms
(11 rows)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tab where c3 between 1 and 10000;
                                                             QUERY PLAN                                                              
-------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tab  (cost=4141.01..672014.67 rows=9697 width=20) (actual time=191.075..10765.038 rows=10000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tab.c3 >= 1) AND (tab.c3 <= 10000))
   Rows Removed by Index Recheck: 99990000
   Heap Blocks: lossy=636943
   Buffers: shared hit=641158
   ->  Bitmap Index Scan on idx_tab_1  (cost=0.00..4138.58 rows=2062044 width=0) (actual time=190.292..190.292 rows=6369430 loops=1)
         Index Cond: ((tab.c3 >= 1) AND (tab.c3 <= 10000))
         Buffers: shared hit=4215
 Planning time: 0.086 ms
 Execution time: 10766.036 ms
(11 rows)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl1 where c3 between 1 and 10000;
                                                           QUERY PLAN                                                            
---------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tbl1  (cost=4137.85..17069.21 rows=10133 width=20) (actual time=150.710..152.040 rows=10000 loops=1)
   Output: c1, c2, c3, c4, c5
   Recheck Cond: ((tbl1.c3 >= 1) AND (tbl1.c3 <= 10000))
   Rows Removed by Index Recheck: 205
   Heap Blocks: lossy=65
   Buffers: shared hit=4280
   ->  Bitmap Index Scan on idx_tbl1_1  (cost=0.00..4135.32 rows=10205 width=0) (actual time=150.692..150.692 rows=650 loops=1)
         Index Cond: ((tbl1.c3 >= 1) AND (tbl1.c3 <= 10000))
         Buffers: shared hit=4215
 Planning time: 0.083 ms
 Execution time: 152.546 ms
(11 rows)

2、數組改命

《索引掃描優化之 - GIN數據重組優化(按元素聚合) 想象在玩多階魔方》

《從一維編排到多維編排,從平麵存儲到3D存儲 - 數據存儲優化之路》

《K-Means 數據聚集算法》

3、時空數據改命

《時間、空間、對象多維屬性 海量數據任意多維 高效檢索 - 阿裏雲RDS PostgreSQL最佳實踐》

4、證券係統改命

《PostgreSQL 時序最佳實踐 - 證券交易係統數據庫設計 - 阿裏雲RDS PostgreSQL最佳實踐》

相關技術

1、列存儲插件 cstore

https://github.com/citusdata/cstore_fdw

https://www.postgresql.org/docs/10/static/fdw-callbacks.html

2、《一個簡單算法可以幫助物聯網,金融 用戶 節約98%的數據存儲成本 (PostgreSQL,Greenplum幫你做到)》

3、《PostgreSQL 物聯網黑科技 - 瘦身幾百倍的索引(BRIN index)》

https://www.postgresql.org/docs/10/static/brin-intro.html

4、metascan是阿裏雲PostgreSQL內核團隊研發的一個數據庫功能,已用於RDS PostgreSQLHybridDB for PostgreSQL,將來亦可整合到存儲引擎層麵,將數據的FILTER下推到存儲層,根據用戶提供的查詢條件,在不建索引的情況下可以減少數據的掃描量,提高效率。

我們已測試,查詢性能有3到5倍的提升(相比不建索引)。同時寫入性能有至少1倍的提升(相比建立索引)。

雲端產品

阿裏雲 RDS PostgreSQL

阿裏雲 HybridDB for PostgreSQL

最後更新:2017-08-21 09:03:17

  上一篇:go  燃!阿裏11篇論文入選IJCAI2017 人工智能領域捷報頻傳
  下一篇:go  MySQL · 源碼分析 · SHUTDOWN過程