阿里云HybridDB for PG实践 - 行存、列存,堆表、AO表的原理和选择

标签

PostgreSQL , Greenplum , 向量计算 , 行存储 , 列存 , AO表


背景

Greenplum支持行存和列存,支持堆表和AO表,那么他们有什么不同,如何选择呢?

行存和列存的原理

1、行存,以行为形式组织存储,一行是一个tuple,存在一起。当需要读取某列时,需要将这列前面的所有列都进行deform,所以访问第一列和访问最后一列的成本实际上是不一样的。

在这篇文档中,有deform的详细介绍。《PostgreSQL 向量化执行插件(瓦片式实现) 10x提速OLAP》

行存小结:

全表扫描要扫描更多的数据块。

压缩比较低。

读取任意列的成本不一样,越靠后的列,成本越高。

不适合向量计算、JIT架构。(简单来说,就是不适合批处理形式的计算)

需要REWRITE表时,需要对全表进行REWRITE,例如加字段有默认值。

2、列存,以列为形式组织存储,每列对应一个或一批文件。读取任一列的成本是一样的,但是如果要读取多列,需要访问多个文件,访问的列越多,开销越大。

列存小结:

压缩比高。

仅仅支持AO存储(后面会将)。

读取任意列的成本是一样的。

非常适合向量计算、JIT架构。对大批量数据的访问和统计,效率更高。

读取很多列时,由于需要访问更多的文件,成本更高。例如查询明细。

需要REWRITE表时,不需要对全表操作,例如加字段有默认值,只是添加字段对应的那个文件。

什么时候选择行存

如果OLTP的需求偏多,例如经常需要查询表的明细(输出很多列),需要更多的更新和删除操作时。可以考虑行存。

什么时候选择列存

如果OLAP的需求偏多,经常需要对数据进行统计时,选择列存。

需要比较高的压缩比时,选择列存。

如果用户有混合需求,可以采用分区表,例如按时间维度的需求分区,近期的数据明细查询多,那就使用行存,对历史的数据统计需求多那就使用列存。

堆表和AO表的原理

1、堆表,实际上就是PG的堆存储,堆表的所有变更都会产生REDO,可以实现时间点恢复。但是堆表不能实现逻辑增量备份(因为表的任意一个数据块都有可能变更,不方便通过堆存储来记录位点。)。

一个事务结束时,通过clog以及REDO来实现它的可靠性。同时支持通过REDO来构建MIRROR节点实现数据冗余。

2、AO表,看名字就知道,只追加的存储,删除更新数据时,通过另一个BITMAP文件来标记被删除的行,通过bit以及偏移对齐来判定AO表上的某一行是否被删除。

事务结束时,需要调用FSYNC,记录最后一次写入对应的数据块的偏移。(并且这个数据块即使只有一条记录,下次再发起事务又会重新追加一个数据块)同时发送对应的数据块给MIRROR实现数据冗余。

因此AO表不适合小事务,因为每次事务结束都会FSYNC,同时事务结束后这个数据块即使有空余也不会被复用。(你可以测试一下,AO表单条提交的IO放大很严重)。

虽然如此,AO表非常适合OLAP场景,批量的数据写入,高压缩比,逻辑备份支持增量备份,因此每次记录备份到的偏移量即可。加上每次备份全量的BITMAP删除标记(很小)。

什么时候选择堆表

当数据写入时,小事务偏多时选择堆表。

当需要时间点恢复时,选择堆表。

什么时候选择AO表

当需要列存时,选择AO表。

当数据批量写入时,选择AO表。

测试对比行存deform和列存的性能差别

1、创建一个函数,用于创建400列的表(行存堆表、AO行存表、AO列存表)。

create or replace function f(name, int, text) returns void as $$
declare
  res text := '';
begin
  for i in 1..$2 loop
    res := res||'c'||i||' int8,';
  end loop;
  res := rtrim(res, ',');
  if $3 = 'ao_col' then
    res := 'create table '||$1||'('||res||') with  (appendonly=true, blocksize=8192, compresstype=none, orientation=column)';
  elsif $3 = 'ao_row' then
    res := 'create table '||$1||'('||res||') with  (appendonly=true, blocksize=8192, orientation=row)';
  elsif $3 = 'heap_row' then
    res := 'create table '||$1||'('||res||') with  (appendonly=false)';
  else
    raise notice 'use ao_col, ao_row, heap_row as $3';
    return;
  end if;
  execute res;
end;
$$ language plpgsql;

2、创建表如下

postgres=# select f('tbl_ao_col', 400, 'ao_col');
postgres=# select f('tbl_ao_row', 400, 'ao_row');
postgres=# select f('tbl_heap_row', 400, 'heap_row');

3、创建1个函数,用于填充数据,其中第一个和最后3个字段为测试数据的字段,其他都填充1。

create or replace function f_ins1(name, int, int8) returns void as $$
declare
  res text := '';
begin
  for i in 1..($2-4) loop
    res := res||'1,';
  end loop;
  res := 'id,'||res;
  res := rtrim(res, ',');
  res := 'insert into '||$1||' select '||res||'id,random()*10000,random()*100000 from generate_series(1,'||$3||') t(id)';
  execute res;
end;
$$ language plpgsql;

4、填充数据

postgres=# select f_ins1('tbl_ao_col',400,1000000);

5、创建1个函数,用于填充数据,其中前4个字段为测试数据的字段,其他都填充1。

create or replace function f_ins2(name, int, int8) returns void as $$
declare
  res text := '';
begin
  for i in 1..($2-4) loop
    res := res||'1,';
  end loop;
  res := 'id,id,random()*10000,random()*100000,'||res;
  res := rtrim(res, ',');
  res := 'insert into '||$1||' select '||res||' from generate_series(1,'||$3||') t(id)';
  execute res;
end;
$$ language plpgsql;

6、填充数据

postgres=# select f_ins1('tbl_ao_col',400,1000000);
 f_ins1
--------  

(1 row)  

postgres=# insert into tbl_ao_row select * from tbl_ao_col;
INSERT 0 1000000
postgres=# insert into tbl_heap_row select * from tbl_ao_col;
INSERT 0 1000000

7、表分析

postgres=# analyze tbl_ao_col ;
ANALYZE
postgres=# analyze tbl_ao_row;
ANALYZE
postgres=# analyze tbl_heap_row;
ANALYZE

8、表大小

postgres=# select pg_size_pretty(pg_relation_size('tbl_ao_col'));
 pg_size_pretty
----------------
 3060 MB
(1 row)  

postgres=# select pg_size_pretty(pg_relation_size('tbl_ao_row'));
 pg_size_pretty
----------------
 3117 MB
(1 row)  

postgres=# select pg_size_pretty(pg_relation_size('tbl_heap_row'));
 pg_size_pretty
----------------
 3473 MB
(1 row)

9、行存堆表,前面几个字段的统计

postgres=# explain analyze select c2,count(*),sum(c3),avg(c3),min(c3),max(c3) from tbl_heap_row group by c2;
                                                                        QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=136132.40..136132.42 rows=1 width=96)
   Rows out:  1 rows at destination with 135 ms to end, start offset by 1.922 ms.
   ->  HashAggregate  (cost=136132.40..136132.42 rows=1 width=96)
         Group By: tbl_heap_row.c2
         Rows out:  1 rows (seg42) with 0.002 ms to first row, 36 ms to end, start offset by 48 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=136132.35..136132.37 rows=1 width=96)
               Hash Key: tbl_heap_row.c2
               Rows out:  48 rows at destination (seg42) with 53 ms to end, start offset by 48 ms.
               ->  HashAggregate  (cost=136132.35..136132.35 rows=1 width=96)
                     Group By: tbl_heap_row.c2
                     Rows out:  Avg 1.0 rows x 48 workers.  Max 1 rows (seg0) with 0.008 ms to first row, 1.993 ms to end, start offset by 48 ms.
                     ->  Seq Scan on tbl_heap_row  (cost=0.00..121134.54 rows=20831 width=16)
                           Rows out:  Avg 20833.3 rows x 48 workers.  Max 20854 rows (seg42) with 40 ms to first row, 73 ms to end, start offset by 50 ms.
 Slice statistics:
   (slice0)    Executor memory: 345K bytes.
   (slice1)    Executor memory: 751K bytes avg x 48 workers, 751K bytes max (seg0).
   (slice2)    Executor memory: 359K bytes avg x 48 workers, 374K bytes max (seg42).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 138.524 ms
(22 rows)

10、行存堆表,末尾几个字段的统计

postgres=# explain analyze select c398,count(*),sum(c399),avg(c399),min(c399),max(c399) from tbl_heap_row group by c398;
                                                                         QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=136576.82..136799.05 rows=9877 width=96)
   Rows out:  10001 rows at destination with 212 ms to end, start offset by 1.917 ms.
   ->  HashAggregate  (cost=136576.82..136799.05 rows=206 width=96)
         Group By: tbl_heap_row.c398
         Rows out:  Avg 208.4 rows x 48 workers.  Max 223 rows (seg17) with 0.001 ms to first row, 70 ms to end, start offset by 14 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=136132.35..136329.89 rows=206 width=96)
               Hash Key: tbl_heap_row.c398
               Rows out:  Avg 8762.2 rows x 48 workers at destination.  Max 9422 rows (seg46) with 93 ms to end, start offset by 48 ms.
               ->  HashAggregate  (cost=136132.35..136132.35 rows=206 width=96)
                     Group By: tbl_heap_row.c398
                     Rows out:  Avg 8762.2 rows x 48 workers.  Max 8835 rows (seg2) with 0.003 ms to first row, 12 ms to end, start offset by 49 ms.
                     ->  Seq Scan on tbl_heap_row  (cost=0.00..121134.54 rows=20831 width=16)
                           Rows out:  Avg 20833.3 rows x 48 workers.  Max 20854 rows (seg42) with 40 ms to first row, 133 ms to end, start offset by 51 ms.
 Slice statistics:
   (slice0)    Executor memory: 377K bytes.
   (slice1)    Executor memory: 1156K bytes avg x 48 workers, 1156K bytes max (seg0).
   (slice2)    Executor memory: 414K bytes avg x 48 workers, 414K bytes max (seg1).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 214.024 ms
(22 rows)

11、行存AO表,前面几个字段的统计

postgres=# explain analyze select c2,count(*),sum(c3),avg(c3),min(c3),max(c3) from tbl_ao_row group by c2;
                                                                  QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=124755.04..124755.07 rows=1 width=96)
   Rows out:  1 rows at destination with 149 ms to end, start offset by 1.890 ms.
   ->  HashAggregate  (cost=124755.04..124755.07 rows=1 width=96)
         Group By: tbl_ao_row.c2
         Rows out:  1 rows (seg42) with 0.004 ms to first row, 55 ms to end, start offset by 64 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=124755.00..124755.02 rows=1 width=96)
               Hash Key: tbl_ao_row.c2
               Rows out:  48 rows at destination (seg42) with 32 ms to end, start offset by 64 ms.
               ->  HashAggregate  (cost=124755.00..124755.00 rows=1 width=96)
                     Group By: tbl_ao_row.c2
                     Rows out:  Avg 1.0 rows x 48 workers.  Max 1 rows (seg0) with 0.001 ms to first row, 46 ms to end, start offset by 59 ms.
                     ->  Append-only Scan on tbl_ao_row  (cost=0.00..109755.00 rows=20834 width=16)
                           Rows out:  Avg 20833.3 rows x 48 workers.  Max 20854 rows (seg42) with 24 ms to end, start offset by 59 ms.
 Slice statistics:
   (slice0)    Executor memory: 345K bytes.
   (slice1)    Executor memory: 770K bytes avg x 48 workers, 770K bytes max (seg0).
   (slice2)    Executor memory: 359K bytes avg x 48 workers, 374K bytes max (seg42).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 152.386 ms
(22 rows)

12、行存AO表,末尾几个字段的统计

postgres=# explain analyze select c398,count(*),sum(c399),avg(c399),min(c399),max(c399) from tbl_ao_row group by c398;
                                                                     QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=125186.01..125401.52 rows=9578 width=96)
   Rows out:  10001 rows at destination with 183 ms to end, start offset by 1.846 ms.
   ->  HashAggregate  (cost=125186.01..125401.52 rows=200 width=96)
         Group By: tbl_ao_row.c398
         Rows out:  Avg 208.4 rows x 48 workers.  Max 223 rows (seg17) with 0.003 ms to first row, 97 ms to end, start offset by 22 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=124755.00..124946.56 rows=200 width=96)
               Hash Key: tbl_ao_row.c398
               Rows out:  Avg 8762.2 rows x 48 workers at destination.  Max 9422 rows (seg46) with 32 ms to end, start offset by 68 ms.
               ->  HashAggregate  (cost=124755.00..124755.00 rows=200 width=96)
                     Group By: tbl_ao_row.c398
                     Rows out:  Avg 8762.2 rows x 48 workers.  Max 8835 rows (seg2) with 0.013 ms to first row, 48 ms to end, start offset by 22 ms.
                     ->  Append-only Scan on tbl_ao_row  (cost=0.00..109755.00 rows=20834 width=16)
                           Rows out:  Avg 20833.3 rows x 48 workers.  Max 20854 rows (seg42) with 22 ms to end, start offset by 71 ms.
 Slice statistics:
   (slice0)    Executor memory: 377K bytes.
   (slice1)    Executor memory: 1144K bytes avg x 48 workers, 1144K bytes max (seg0).
   (slice2)    Executor memory: 414K bytes avg x 48 workers, 414K bytes max (seg0).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 184.723 ms
(22 rows)

13、列存AO表,前面几个字段的统计

postgres=# explain analyze select c2,count(*),sum(c3),avg(c3),min(c3),max(c3) from tbl_ao_col group by c2;
                                                                    QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=122928.04..122928.07 rows=1 width=96)
   Rows out:  1 rows at destination with 104 ms to end, start offset by 1.878 ms.
   ->  HashAggregate  (cost=122928.04..122928.07 rows=1 width=96)
         Group By: tbl_ao_col.c2
         Rows out:  1 rows (seg42) with 0.003 ms to first row, 18 ms to end, start offset by 55 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=122928.00..122928.02 rows=1 width=96)
               Hash Key: tbl_ao_col.c2
               Rows out:  48 rows at destination (seg42) with 30 ms to end, start offset by 55 ms.
               ->  HashAggregate  (cost=122928.00..122928.00 rows=1 width=96)
                     Group By: tbl_ao_col.c2
                     Rows out:  Avg 1.0 rows x 48 workers.  Max 1 rows (seg0) with 0.007 ms to first row, 3.991 ms to end, start offset by 54 ms.
                     ->  Append-only Columnar Scan on tbl_ao_col  (cost=0.00..107928.00 rows=20834 width=16)
                           Rows out:  0 rows (seg0) with 40 ms to end, start offset by 56 ms.
 Slice statistics:
   (slice0)    Executor memory: 345K bytes.
   (slice1)    Executor memory: 903K bytes avg x 48 workers, 903K bytes max (seg0).
   (slice2)    Executor memory: 359K bytes avg x 48 workers, 374K bytes max (seg42).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 106.859 ms
(22 rows)

14、列存AO表,末尾几个字段的统计

postgres=# explain analyze select c398,count(*),sum(c399),avg(c399),min(c399),max(c399) from tbl_ao_col group by c398;
                                                                       QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Motion 48:1  (slice2; segments: 48)  (cost=123364.18..123582.28 rows=9693 width=96)
   Rows out:  10001 rows at destination with 120 ms to end, start offset by 1.921 ms.
   ->  HashAggregate  (cost=123364.18..123582.28 rows=202 width=96)
         Group By: tbl_ao_col.c398
         Rows out:  Avg 208.4 rows x 48 workers.  Max 223 rows (seg17) with 0.001 ms to first row, 54 ms to end, start offset by 35 ms.
         ->  Redistribute Motion 48:48  (slice1; segments: 48)  (cost=122928.00..123121.86 rows=202 width=96)
               Hash Key: tbl_ao_col.c398
               Rows out:  Avg 8762.2 rows x 48 workers at destination.  Max 9422 rows (seg46) with 31 ms to end, start offset by 63 ms.
               ->  HashAggregate  (cost=122928.00..122928.00 rows=202 width=96)
                     Group By: tbl_ao_col.c398
                     Rows out:  Avg 8762.2 rows x 48 workers.  Max 8835 rows (seg2) with 0.004 ms to first row, 8.004 ms to end, start offset by 82 ms.
                     ->  Append-only Columnar Scan on tbl_ao_col  (cost=0.00..107928.00 rows=20834 width=16)
                           Rows out:  0 rows (seg0) with 28 ms to end, start offset by 64 ms.
 Slice statistics:
   (slice0)    Executor memory: 377K bytes.
   (slice1)    Executor memory: 1272K bytes avg x 48 workers, 1272K bytes max (seg0).
   (slice2)    Executor memory: 414K bytes avg x 48 workers, 414K bytes max (seg0).
 Statement statistics:
   Memory used: 128000K bytes
 Settings:  optimizer=off
 Optimizer status: legacy query optimizer
 Total runtime: 122.173 ms
(22 rows)

小结,对于非分布键的分组聚合请求,Greenplum采用了3阶段聚合:

第一阶段,在SEGMENT本地聚合。(需要扫描所有数据,这里不同存储,前面的列和后面的列的差别就体现出来了,行存储的deform开销,在对后面的列进行统计时性能影响很明显。)

第二阶段,根据分组字段,重分布数据。(需要重分布需要用到的字段。)

第三阶段,在SEGMENT本地聚合。(需要对重分布后的数据进行聚合。)

第四阶段,返回结果。

参考

《PostgreSQL 向量化执行插件(瓦片式实现) 10x提速OLAP》

时间: 2024-12-03 10:43:41

阿里云HybridDB for PG实践 - 行存、列存,堆表、AO表的原理和选择的相关文章

阿里云HybridDB for PG实践 - 列存储加字段和默认值

标签 PostgreSQL , Greenplum , 相对偏移 , 列存储 , appendonly , AO表 背景 Greenplum的Append only table支持更新.删除.通过什么支持呢?bitmap文件,标记被删除的行. 因此在更新,删除后,数据可能膨胀. 另一方面,列存储每列一个文件,同一行通过偏移对应起来.例如INT8的两个字段,通过偏移很快能找到某一行的A列对应的B列. 接下来谈谈加字段,在加字段时,AO表示不会REWRITE TABLE的.如果AO表以及有一些垃圾(

如何检测、清理Greenplum垃圾 - 阿里云HybridDB for PG最佳实践

标签 PostgreSQL , Greenplum , HDB for PG 背景 Greenplum通过多版本支持数据的删除和更新的并发和回滚,在删除数据时(使用DELETE删除),对记录的头部xmax值进行标记.在删除记录时,对记录的头部进行标记,同时插入新的版本. 这一就会导致一个问题,如果用户经常删除和插入或更新数据,表和索引都会膨胀. PostgreSQL是通过HOT技术以及autovacuum来避免或减少垃圾的.但是Greenplum没有自动回收的worker进程,所以需要人为的触发

动态输出(ToB海量日志转换业务) - 阿里云HybridDB for PostgreSQL最佳实践

标签 PostgreSQL , UDF , 动态格式 , format , JOIN , OSS外部表 背景 有一些业务需要将数据归类动态的输出,比如一些公共日志服务,所有用户的日志都被统一的按格式记录到一起,但是每个最终用户关心的字段都不一样,甚至每个用户对数据转换的需求都不一样. 比如这个业务: <日增量万亿+级 实时分析.数据规整 - 阿里云HybridDB for PostgreSQL最佳实践> 一.需求 1.可以根据ToB的用户的定义,输出不同的格式. 2.每个ToB的用户,写入到一

HybridDB for PostgreSQL 列存表(AO表)的膨胀、垃圾检查与空间收缩

标签 PostgreSQL , Greenplum , 垃圾检测 , 膨胀 , 列存表 , gp_appendonly_compaction_threshold 背景 Greenplum支持行存储(堆存储)与AO存储,堆存储的垃圾回收和膨胀检测方法请参考: <如何检测.清理Greenplum膨胀.垃圾 - 阿里云HybridDB for PG最佳实践> 对于AO存储,虽然是appendonly,但实际上GP是支持DELETE和UPDATE的,被删除或更新的行,通过BITMAP来标记. AO存储

每天万亿+级 实时分析、数据规整 - 阿里云HybridDB for PostgreSQL最佳实践

背景 横看成岭侧成峰, 远近高低各不同. 不识庐山真面目, 只缘身在此山中. 不同的视角我们所看到的物体是不一样的, http://t.m.china.com.cn/convert/c_ovWL9w.html 图为墨西哥城放射状的街区广场. 图为西班牙迷宫般的果树漩涡. 地心说和日心说也是视角不同所呈现的. 实际上数据也有这样,我们每天产生海量的数据,有各种属性,以每个属性为视角(分组.归类.聚合),看到的是对应属性为中心的数据. 对应的业务场景也非常多,例如: 1.物联网, 每个传感器有很多属

Greenplum 空间(GIS)数据检索 B-Tree &amp; GiST 索引实践 - 阿里云HybridDB for PostgreSQL最佳实践

标签 PostgreSQL , GIS , PostGIS , Greenplum , 空间检索 , GiST , B-Tree , geohash 背景 气象数据.地震数据.室内定位.室外定位.手机.车联网.还有我们最喜欢的"左划不喜欢.右划喜欢",越来越多的位置属性的数据.将来会越来越多. 基于GIS的数据分析.OLTP业务也越来越受到决策者的青睐,例如商场的选址决策,O2O的广告营销等.有很多基于多边形.时间.用户对象属性过滤的需求. 阿里云HybridDB for Postgr

阿里云HybridDB for PostgreSQL内存与负载管理(resource queue)实践

标签 PostgreSQL , Greenplum , 阿里云HybridDB for PostgreSQL , 内存管理 , OOM , 操作系统内核参数 , 资源队列 , 数据库内存保护参数 背景 Greenplum是一个重计算和重资源的MPP数据库,可谓有多少资源就能消耗多少资源,带来的好处是处理速度变快了,坏处就是容易用超. CPU.网络.硬盘用超的话,关系不大,因为大不了就是到硬件瓶颈,但是内存用超的话会带来较大的负面影响,例如操作系统OOM用户进程,导致数据库崩溃等. 如果要达到非常

解密上帝之手 - 阿里云HDB for PG特性(数据改命与任意列高效过滤)

标签 PostgreSQL , metascan , 块级过滤 , 块级统计信息 , BATCH级统计信息 , brin , 区间索引 , 块级索引 , batch级索引 , 数据编排 , 存储计算分离 , 混合编排 , 分段编排 背景 数据也有生辰八字,你信吗?列与列之间,行与行之间,元素与元素之间如何相生相克?查询慢?不要信什么这都是上天注定的,如何给数据改运?看完本文,你也可以做到. 一份天赋,九份努力.缘分天注定.命由天定.又有说我命由我不由天的.看样子中国古人对先天注定的东西研究还挺透

(新零售)商户网格化运营 - 阿里云RDS PostgreSQL最佳实践

标签 PostgreSQL , PostGIS , 地理位置 , KNN , 近邻检索 , 网格检索 , polygon中心点 , 半径搜索 背景 伟大的马老师说: "纯电商时代很快会结束,未来的十年.二十年,没有电子商务这一说,只有新零售这一说,也就是说线上线下和物流必须结合在一起,才能诞生真正的新零售" 线上是指云平台,线下是指销售门店或生产商,新物流消灭库存,减少囤货量. 电子商务平台消失是指,现有的电商平台分散,每个人都有自己的电商平台,不再入驻天猫.京东.亚马逊大型电子商务平