PostgreSQL 9.5 新特性之 - 水平分片架构与实践

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: PostgreSQL 9.5 在foreign data wrapper这方面有了几块非常好的功能增强:1. 支持创建外部表约束2. 优化器支持外部表查询下推3. 外部表支持继承4. 支持import foreign schema一键创建外部表使用前3点增强的技术点可以支持数据库的水平分片,可以把.

PostgreSQL 9.5 在foreign data wrapper这方面有了几块非常好的功能增强:

  1. 支持创建外部表约束
  2. 优化器支持外部表查询下推
  3. 外部表支持继承
  4. 支持import foreign schema一键创建外部表
    使用前3点增强的技术点可以支持数据库的水平分片,可以把PostgreSQL 9.5作为master,在其他版本作为数据节点。从而实现水平分库的目的。

这种分片技术相比中间件分片技术的好处:

  1. 支持跨库JOIN
  2. 支持绑定变量
  3. 支持ACID
  4. 支持分布式事务(不需要用户干预2PC)
  5. 支持master节点水平扩展
  6. 支持segment节点水平扩展
  7. 支持函数和存储过程

架构如图:
架构1:
维度表存储在上游节点,好处是JOIN时效率更高,缺点是当上层节点需要部署多个时,需要在上层节点之间同步维度表(可以使用前端同步或后端同步的方法),同时如果维度数据量大或者DML频繁的话master库会比较重。
前端同步可以使用外部表的方式,使用触发器实现。insert,update,delete通过父表触发在所有子表执行。SELECT时查询本地表不走触发器。
后端同步可以使用LOGICAL DECODE。
_
架构2:
维度表存储在分片节点,可以选择任一分片节点存储任一维度表。
缺点是JOIN时数据需要传到上层节点,效率低。
还有一个缺点是数据节点可能不平衡。
当维度表使用多副本时,不存在不平横的问题。这种情况下查询指定任意节点,增删改则指定所有节点。
_1
HA架构:
_2

上层节点使用9.5以上的版本,用于存放维度表(全局表),以及数据分片的定义,数据路由算法。
数据分片的定义包括继承关系,分布列约束,外部表定义,插入触发器。
下层节点使用的数据库版本没有要求,用于存放数据分片。

下面是一组测试,创建5个数据库,1个master库用于存放全局数据和数据分片的定义,数据路由算法;4个下层节点数据库,用于存放数据分片;

master=# create database db0;  
master=# create database db1;  
master=# create database db2;  
master=# create database db3;  
master=# create database master;  

连接到master库,创建外部server:

d3=# \c master  
master=# create extension postgres_fdw;  
master=# create server db0 foreign data wrapper postgres_fdw options (hostaddr '127.0.0.1', port '1923', dbname 'db0');  
master=# create server db1 foreign data wrapper postgres_fdw options (hostaddr '127.0.0.1', port '1923', dbname 'db1');  
master=# create server db2 foreign data wrapper postgres_fdw options (hostaddr '127.0.0.1', port '1923', dbname 'db2');  
master=# create server db3 foreign data wrapper postgres_fdw options (hostaddr '127.0.0.1', port '1923', dbname 'db3');  

创建user mapping:

master=# create user mapping for postgres server db0 options (user 'postgres', password 'postgres');  
master=# create user mapping for postgres server db1 options (user 'postgres', password 'postgres');  
master=# create user mapping for postgres server db2 options (user 'postgres', password 'postgres');  
master=# create user mapping for postgres server db3 options (user 'postgres', password 'postgres');  

连接到分片节点,创建分片表(表名请随意):

master=# \c db0  
db0=# create table tbl0(id int primary key, info text, crt_time timestamp);  
alter table tbl0 add constraint ck1 check (abs(mod(id,4))=0);  
master=# \c db1  
db1=# create table tbl1(id int primary key, info text, crt_time timestamp);  
alter table tbl1 add constraint ck1 check (abs(mod(id,4))=1);  
master=# \c db2  
db2=# create table tbl2(id int primary key, info text, crt_time timestamp);  
alter table tbl2 add constraint ck1 check (abs(mod(id,4))=2);  
master=# \c db3  
db3=# create table tbl3(id int primary key, info text, crt_time timestamp);  
alter table tbl3 add constraint ck1 check (abs(mod(id,4))=3);  

连接到主节点,创建外部表,这里使用了import foreign schema语法,一键创建:

db3=# \c master  
You are now connected to database "master" as user "postgres".  
master=# import FOREIGN SCHEMA public from server db0 into public;   
IMPORT FOREIGN SCHEMA  
master=# import FOREIGN SCHEMA public from server db1 into public;   
IMPORT FOREIGN SCHEMA  
master=# import FOREIGN SCHEMA public from server db2 into public;   
IMPORT FOREIGN SCHEMA  
master=# import FOREIGN SCHEMA public from server db3 into public;   
IMPORT FOREIGN SCHEMA  
master=# \det  
 List of foreign tables  
 Schema | Table | Server   
--------+-------+--------  
 public | tbl0  | db0  
 public | tbl1  | db1  
 public | tbl2  | db2  
 public | tbl3  | db3  
(4 rows)  

创建主表,用户操作主表即可。(当然用户也可以直接操作子表,PostgreSQL不拦你)

master=# create table tbl(id int, info text, crt_time timestamp);  
CREATE TABLE  

设置外部表继承关系,继承到主表下面。

master=# alter foreign table tbl0 inherit tbl;  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl1 inherit tbl;  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl2 inherit tbl;  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl3 inherit tbl;  
ALTER FOREIGN TABLE  

创建外部表的约束,约束即路由算法的一部分。
注意,带约束条件的SQL,数据库会自动选择对应的外部表进行操作。
不带约束条件的SQL,数据库会选择所有节点操作。
所以建议每条SQL都带上约束条件。

master=# alter foreign table tbl0 add constraint ck_tbl0 check (abs(mod(id,4))=0);  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl1 add constraint ck_tbl1 check (abs(mod(id,4))=1);  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl2 add constraint ck_tbl2 check (abs(mod(id,4))=2);  
ALTER FOREIGN TABLE  
master=# alter foreign table tbl3 add constraint ck_tbl3 check (abs(mod(id,4))=3);  
ALTER FOREIGN TABLE  

带约束条件abs(mod(id, 4)) = (abs(mod(100, 4)))的SQL,选择了对应的外部表进行操作。

master=# explain select * from tbl where id=100 and abs(mod(id, 4)) = (abs(mod(100, 4)));  
                            QUERY PLAN                               
-------------------------------------------------------------------  
 Append  (cost=0.00..134.10 rows=2 width=44)  
   ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
         Filter: ((id = 100) AND (abs(mod(id, 4)) = 0))  
   ->  Foreign Scan on tbl0  (cost=100.00..134.10 rows=1 width=44)  
(4 rows)  
  
master=# explain select * from tbl where id=101 and abs(mod(id, 4)) = (abs(mod(101, 4)));  
                            QUERY PLAN                               
-------------------------------------------------------------------  
 Append  (cost=0.00..134.10 rows=2 width=44)  
   ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
         Filter: ((id = 101) AND (abs(mod(id, 4)) = 1))  
   ->  Foreign Scan on tbl1  (cost=100.00..134.10 rows=1 width=44)  
(4 rows)  

不带约束条件abs(mod(id, 4)) = (abs(mod(100, 4)))的SQL,选择了所有外部表进行操作。

master=# explain select * from tbl where id=100;  
                            QUERY PLAN                               
-------------------------------------------------------------------  
 Append  (cost=0.00..500.68 rows=25 width=44)  
   ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
         Filter: (id = 100)  
   ->  Foreign Scan on tbl0  (cost=100.00..125.17 rows=6 width=44)  
   ->  Foreign Scan on tbl1  (cost=100.00..125.17 rows=6 width=44)  
   ->  Foreign Scan on tbl2  (cost=100.00..125.17 rows=6 width=44)  
   ->  Foreign Scan on tbl3  (cost=100.00..125.17 rows=6 width=44)  
(7 rows)  
  
master=# explain select count(*),sum(id),avg(id+id) from tbl;  
                                QUERY PLAN                                   
---------------------------------------------------------------------------  
 Aggregate  (cost=908.01..908.02 rows=1 width=4)  
   ->  Append  (cost=0.00..791.00 rows=11701 width=4)  
         ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=4)  
         ->  Foreign Scan on tbl0  (cost=100.00..197.75 rows=2925 width=4)  
         ->  Foreign Scan on tbl1  (cost=100.00..197.75 rows=2925 width=4)  
         ->  Foreign Scan on tbl2  (cost=100.00..197.75 rows=2925 width=4)  
         ->  Foreign Scan on tbl3  (cost=100.00..197.75 rows=2925 width=4)  
(7 rows)  

创建插入路由触发器函数:

master=# create or replace function f_tbl_ins() returns trigger as 
$$
  
declare  
begin  
  case abs(mod(NEW.id, 4))   
    when 0 then  
      insert into tbl0 (id, info, crt_time) values (NEW.*);  
    when 1 then  
      insert into tbl1 (id, info, crt_time) values (NEW.*);  
    when 2 then  
      insert into tbl2 (id, info, crt_time) values (NEW.*);  
    when 3 then  
      insert into tbl3 (id, info, crt_time) values (NEW.*);  
    else  
      return null;  
  end case;  
    return null;  
end;  

$$
 language plpgsql;  

创建插入触发器:

master=# create trigger tg1 before insert on tbl for each row execute procedure f_tbl_ins();  
CREATE TRIGGER  

测试插入路由是否正确:

master=# insert into tbl values (1,'abc',now());  
INSERT 0 0  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
(1 row)  
  
master=# insert into tbl values (2,'abc',now());  
INSERT 0 0  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
(2 rows)  
  
master=# select * from tbl where id=2;  
 id | info |          crt_time            
----+------+----------------------------  
  2 | abc  | 2016-02-23 09:17:55.065332  
(1 row)  
  
master=# select * from tbl where id=2 and abs(mod(id, 4))=abs(mod(2, 4));  
 id | info |          crt_time            
----+------+----------------------------  
  2 | abc  | 2016-02-23 09:17:55.065332  
(1 row)  

带约束条件abs(mod(id, 4)) = (abs(mod(100, 4)))的SQL,选择了对应的外部表进行操作。

master=# explain select * from tbl where id=2 and abs(mod(id, 4))=abs(mod(2, 4));  
                            QUERY PLAN                               
-------------------------------------------------------------------  
 Append  (cost=0.00..134.10 rows=2 width=44)  
   ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
         Filter: ((id = 2) AND (abs(mod(id, 4)) = 2))  
   ->  Foreign Scan on tbl2  (cost=100.00..134.10 rows=1 width=44)  
(4 rows)  

空值操作,在触发器中已经绕过:

master=# insert into tbl values (null,'abc',now());  
INSERT 0 0  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
(2 rows)  

支持分布式事务,看执行计划,远程用了for update,然后在上层先提交远程节点,再提交本地节点:

master=# explain (verbose) update tbl set info='new' where id=2 and abs(mod(id, 4))=abs(mod(2, 4));  
                                                       QUERY PLAN                                                         
------------------------------------------------------------------------------------------------------------------------  
 Update on public.tbl  (cost=0.00..149.02 rows=2 width=18)  
   Update on public.tbl  
   Foreign Update on public.tbl2  
     Remote SQL: UPDATE public.tbl2 SET info = $2 WHERE ctid = $1  
   ->  Seq Scan on public.tbl  (cost=0.00..0.00 rows=1 width=18)  
         Output: tbl.id, 'new'::text, tbl.crt_time, tbl.ctid  
         Filter: ((tbl.id = 2) AND (abs(mod(tbl.id, 4)) = 2))  
   ->  Foreign Scan on public.tbl2  (cost=100.00..149.02 rows=1 width=18)  
         Output: tbl2.id, 'new'::text, tbl2.crt_time, tbl2.ctid  
         Remote SQL: SELECT id, crt_time, ctid FROM public.tbl2 WHERE ((id = 2)) AND ((abs(mod(id, 4)) = 2)) FOR UPDATE  
(10 rows)  
  
master=# explain (verbose) delete from tbl where id=2 and abs(mod(id, 4))=abs(mod(2, 4));  
                                                QUERY PLAN                                                  
----------------------------------------------------------------------------------------------------------  
 Delete on public.tbl  (cost=0.00..164.62 rows=2 width=6)  
   Delete on public.tbl  
   Foreign Delete on public.tbl2  
     Remote SQL: DELETE FROM public.tbl2 WHERE ctid = $1  
   ->  Seq Scan on public.tbl  (cost=0.00..0.00 rows=1 width=6)  
         Output: tbl.ctid  
         Filter: ((tbl.id = 2) AND (abs(mod(tbl.id, 4)) = 2))  
   ->  Foreign Scan on public.tbl2  (cost=100.00..164.62 rows=1 width=6)  
         Output: tbl2.ctid  
         Remote SQL: SELECT ctid FROM public.tbl2 WHERE ((id = 2)) AND ((abs(mod(id, 4)) = 2)) FOR UPDATE  
(10 rows)  

可以回退外部表SQL:

master=# begin ;  
BEGIN  
master=# insert into tbl0 values (0,'abc',now());  
INSERT 0 1  
master=# rollback;  
ROLLBACK  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
(2 rows)  

有跨库事务时,支持全局一致性:

master=# begin;  
BEGIN  
master=# insert into tbl values(3,'new',now());  
INSERT 0 0  
master=# insert into tbl values(1,'new',now());  
ERROR:  duplicate key value violates unique constraint "pk"  
DETAIL:  Key (id)=(1) already exists.  
CONTEXT:  Remote SQL command: INSERT INTO public.tbl1(id, info, crt_time) VALUES ($1, $2, $3)  
SQL statement "insert into tbl1 (id, info, crt_time) values (NEW.*)"  
PL/pgSQL function f_tbl_ins() line 8 at SQL statement  
master=# end;  
ROLLBACK  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
(2 rows)  

有跨库事务和本地事务时,支持全局一致性:

master=# create table test(id int primary key, info text);  
CREATE TABLE  
master=# begin;  
BEGIN  
master=# insert into test values (1,'abc');  
INSERT 0 1  
master=# insert into tbl values(3,'new',now());  
INSERT 0 0  
master=# insert into test values (1,'abc');  
ERROR:  duplicate key value violates unique constraint "test_pkey"  
DETAIL:  Key (id)=(1) already exists.  
master=# end;  
ROLLBACK  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
(2 rows)  
  
master=# select * from test;  
 id | info   
----+------  
(0 rows)  

支持绑定变量:

master=# prepare p1 (int,text,timestamp) as insert into tbl values ($1,$2,$3);  
PREPARE  
master=# prepare p2 (int,int) as select * from tbl where id=$1 and abs(mod($1,4))=$2;  
PREPARE  
master=# prepare p3 (int,int,text,timestamp) as update tbl set info=$3,crt_time=$4 where id=$1 and abs(mod($1,4))=$2;  
PREPARE  
master=# execute p1(1,'abc',now());  
ERROR:  duplicate key value violates unique constraint "pk"  
DETAIL:  Key (id)=(1) already exists.  
CONTEXT:  Remote SQL command: INSERT INTO public.tbl1(id, info, crt_time) VALUES ($1, $2, $3)  
SQL statement "insert into tbl1 (id, info, crt_time) values (NEW.*)"  
PL/pgSQL function f_tbl_ins() line 8 at SQL statement  
master=# execute p1(3,'abc',now());  
INSERT 0 0  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
  2 | abc  | 2016-02-23 09:17:55.065332  
  3 | abc  | 2016-02-23 09:56:00.835324  
(3 rows)  
  
master=# execute p1(4,'abc',now());  
INSERT 0 0  
master=# execute p1(5,'abc',now());  
INSERT 0 0  
master=# execute p1(6,'abc',now());  
INSERT 0 0  
master=# execute p1(7,'abc',now());  
INSERT 0 0  
master=# execute p1(8,'abc',now());  
INSERT 0 0  
master=# execute p1(9,'abc',now());  
INSERT 0 0  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  4 | abc  | 2016-02-23 09:56:20.159337  
  8 | abc  | 2016-02-23 09:56:31.034317  
  1 | abc  | 2016-02-23 09:17:43.054333  
  5 | abc  | 2016-02-23 09:56:24.392312  
  9 | abc  | 2016-02-23 09:56:33.303365  
  2 | abc  | 2016-02-23 09:17:55.065332  
  6 | abc  | 2016-02-23 09:56:26.560318  
  3 | abc  | 2016-02-23 09:56:00.835324  
  7 | abc  | 2016-02-23 09:56:28.740312  
(9 rows)  
  
master=# execute p2(1,1);  
 id | info |          crt_time            
----+------+----------------------------  
  1 | abc  | 2016-02-23 09:17:43.054333  
(1 row)  
  
master=# execute p2(10,2);  
 id | info | crt_time   
----+------+----------  
(0 rows)  
  
master=# execute p2(1,2);  
 id | info | crt_time   
----+------+----------  
(0 rows)  
  
master=# execute p2(2,2);  
 id | info |          crt_time            
----+------+----------------------------  
  2 | abc  | 2016-02-23 09:17:55.065332  
(1 row)  
  
master=# execute p3(1,1,'test',now());  
UPDATE 1  
master=# select * from tbl;  
 id | info |          crt_time            
----+------+----------------------------  
  4 | abc  | 2016-02-23 09:56:20.159337  
  8 | abc  | 2016-02-23 09:56:31.034317  
  5 | abc  | 2016-02-23 09:56:24.392312  
  9 | abc  | 2016-02-23 09:56:33.303365  
  1 | test | 2016-02-23 09:57:12.126359  
  2 | abc  | 2016-02-23 09:17:55.065332  
  6 | abc  | 2016-02-23 09:56:26.560318  
  3 | abc  | 2016-02-23 09:56:00.835324  
  7 | abc  | 2016-02-23 09:56:28.740312  
(9 rows)  

使用xmin, xmax验证外部表因为事务中其他SQL或其他节点执行失败导致的事务回滚的情况,确保全局事务一致:

db0=# select tableoid,ctid,xmin,xmax,* from tbl0;  
 tableoid |  ctid  |   xmin   | xmax | id | info |          crt_time            
----------+--------+----------+------+----+------+----------------------------  
    20280 | (0,11) | 38232587 |    0 |  4 | test | 2016-02-23 11:25:20.440349  
    20280 | (0,12) | 38232587 |    0 |  8 | test | 2016-02-23 11:25:20.440349  
(2 rows)  

注意master节点查看到的外部表的xmin, xmax是不准确的,已经提交了这个BUG给社区。

db0=# \c master  
You are now connected to database "master" as user "postgres".  
master=# select tableoid,ctid,xmin,xmax,* from tbl0;  
 tableoid |  ctid  | xmin |    xmax    | id | info |          crt_time            
----------+--------+------+------------+----+------+----------------------------  
    20304 | (0,11) |  192 | 4294967295 |  4 | test | 2016-02-23 11:25:20.440349  
    20304 | (0,12) |  192 | 4294967295 |  8 | test | 2016-02-23 11:25:20.440349  
(2 rows)  

使用以下SQL,在一条SQL中跨库更新多条记录,其中一条涉及到ID从7变成6,会违反数据库分片的表约束,导致SQL失败,看看这种情况下是否能保证全局事务一致性。

master=# update tbl set id=(case id when 4 then 4 when 3 then 3 when 7 then 6 else id end) ;  
ERROR:  new row for relation "tbl3" violates check constraint "ck1"  
DETAIL:  Failing row contains (6, test, 2016-02-23 11:25:20.440349).  
CONTEXT:  Remote SQL command: UPDATE public.tbl3 SET id = $2 WHERE ctid = $1  
master=# select tableoid,ctid,xmin,xmax,* from tbl4;  
ERROR:  relation "tbl4" does not exist  
LINE 1: select tableoid,ctid,xmin,xmax,* from tbl4;  
                                              ^  

查看XID:

master=# select tableoid,ctid,xmin,xmax,* from tbl0;  
 tableoid |  ctid  | xmin |    xmax    | id | info |          crt_time            
----------+--------+------+------------+----+------+----------------------------  
    20304 | (0,11) |  192 | 4294967295 |  4 | test | 2016-02-23 11:25:20.440349  
    20304 | (0,12) |  192 | 4294967295 |  8 | test | 2016-02-23 11:25:20.440349  
(2 rows)  

可以看到xmax值已经变更了,说明发生了回退,从而确保了全局事务的一致性。

master=# \c db0  
You are now connected to database "db0" as user "postgres".  
db0=# select tableoid,ctid,xmin,xmax,* from tbl0;  
 tableoid |  ctid  |   xmin   |   xmax   | id | info |          crt_time            
----------+--------+----------+----------+----+------+----------------------------  
    20280 | (0,11) | 38232587 | 38232588 |  4 | test | 2016-02-23 11:25:20.440349  
    20280 | (0,12) | 38232587 | 38232588 |  8 | test | 2016-02-23 11:25:20.440349  
(2 rows)  

外部表和外部表的跨库JOIN

master=# explain select * from tbl, t where t.id=tbl.id and tbl.id=1 and abs(mod(tbl.id,4))=1;  
                                  QUERY PLAN                                     
-------------------------------------------------------------------------------  
 Nested Loop  (cost=0.00..635.41 rows=50 width=88)  
   ->  Append  (cost=0.00..500.68 rows=25 width=44)  
         ->  Seq Scan on t  (cost=0.00..0.00 rows=1 width=44)  
               Filter: (id = 1)  
         ->  Foreign Scan on db0_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db1_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db2_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db3_t  (cost=100.00..125.17 rows=6 width=44)  
   ->  Materialize  (cost=0.00..134.11 rows=2 width=44)  
         ->  Append  (cost=0.00..134.10 rows=2 width=44)  
               ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
                     Filter: ((id = 1) AND (abs(mod(id, 4)) = 1))  
               ->  Foreign Scan on tbl1  (cost=100.00..134.10 rows=1 width=44)  
(13 rows)  

外部表,全局表的JOIN

master=# explain select * from tbl, t, test where t.id=tbl.id and test.id=t.id and tbl.id=1 and abs(mod(tbl.id,4))=1;  
                                       QUERY PLAN                                          
-----------------------------------------------------------------------------------------  
 Nested Loop  (cost=0.15..643.60 rows=50 width=124)  
   ->  Append  (cost=0.00..500.68 rows=25 width=44)  
         ->  Seq Scan on t  (cost=0.00..0.00 rows=1 width=44)  
               Filter: (id = 1)  
         ->  Foreign Scan on db0_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db1_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db2_t  (cost=100.00..125.17 rows=6 width=44)  
         ->  Foreign Scan on db3_t  (cost=100.00..125.17 rows=6 width=44)  
   ->  Materialize  (cost=0.15..142.30 rows=2 width=80)  
         ->  Nested Loop  (cost=0.15..142.29 rows=2 width=80)  
               ->  Index Scan using test_pkey on test  (cost=0.15..8.17 rows=1 width=36)  
                     Index Cond: (id = 1)  
               ->  Append  (cost=0.00..134.10 rows=2 width=44)  
                     ->  Seq Scan on tbl  (cost=0.00..0.00 rows=1 width=44)  
                           Filter: ((id = 1) AND (abs(mod(id, 4)) = 1))  
                     ->  Foreign Scan on tbl1  (cost=100.00..134.10 rows=1 width=44)  
(16 rows)  

扩展能力和性能:
master和数据分片都可以水平扩展。
性能可以随着节点数的增加线性提升。
更适合OLTP。OLAP目前还是MPP做得比较好。
如果要下推JOIN,目前的方法是使用数据节点视图,将JOIN封装在视图中,在上层节点建立视图的外部表,通过访问这类外部表来实现JOIN的下推。未来PG会提供JOIN下推的功能,不需要这么麻烦。

其他
truncate 目前不支持外部表
JOIN目前不能下推到数据节点执行
条件可以下推到数据节点

不要走开,马上下一篇会介绍利用PostgreSQL特性实现 Oracle 水平分库的方案和技术实现。
没错,是 Oracle 水平分库,而且不限 Oracle 的版本。
文章来咯:
https://yq.aliyun.com/articles/6637

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
18天前
|
机器学习/深度学习 安全 算法
十大主流联邦学习框架:技术特性、架构分析与对比研究
联邦学习(FL)是保障数据隐私的分布式模型训练关键技术。业界开发了多种开源和商业框架,如TensorFlow Federated、PySyft、NVFlare、FATE、Flower等,支持模型训练、数据安全、通信协议等功能。这些框架在灵活性、易用性、安全性和扩展性方面各有特色,适用于不同应用场景。选择合适的框架需综合考虑开源与商业、数据分区支持、安全性、易用性和技术生态集成等因素。联邦学习已在医疗、金融等领域广泛应用,选择适配具体需求的框架对实现最优模型性能至关重要。
251 79
十大主流联邦学习框架:技术特性、架构分析与对比研究
|
3天前
|
存储 SQL 监控
转转平台IM系统架构设计与实践(二):详细设计与实现
以转转IM架构为起点,介绍IM相关组件以及组件间的关系;以IM登陆和发消息的数据流转为跑道,介绍IM静态数据结构、登陆和发消息时的动态数据变化;以IM常见问题为风景,介绍保证IM实时性、可靠性、一致性的一般方案;以高可用、高并发为终点,介绍保证IM系统稳定及性能的小技巧。
17 6
|
23天前
|
存储 缓存 关系型数据库
社交软件红包技术解密(六):微信红包系统的存储层架构演进实践
微信红包本质是小额资金在用户帐户流转,有发、抢、拆三大步骤。在这个过程中对事务有高要求,所以订单最终要基于传统的RDBMS,这方面是它的强项,最终订单的存储使用互联网行业最通用的MySQL数据库。支持事务、成熟稳定,我们的团队在MySQL上有长期技术积累。但是传统数据库的扩展性有局限,需要通过架构解决。
62 18
|
28天前
|
存储 缓存 监控
ClickHouse 架构原理及核心特性详解
ClickHouse 是由 Yandex 开发的开源列式数据库,专为 OLAP 场景设计,支持高效的大数据分析。其核心特性包括列式存储、字段压缩、丰富的数据类型、向量化执行和分布式查询。ClickHouse 通过多种表引擎(如 MergeTree、ReplacingMergeTree、SummingMergeTree)优化了数据写入和查询性能,适用于电商数据分析、日志分析等场景。然而,它在事务处理、单条数据更新删除及内存占用方面存在不足。
278 21
|
28天前
|
存储 消息中间件 druid
Druid 架构原理及核心特性详解
Druid 是一个分布式、支持实时多维OLAP分析的列式存储数据处理系统,适用于高速实时数据读取和灵活的多维数据分析。它通过Segment、Datasource等元数据概念管理数据,并依赖Zookeeper、Hadoop和Kafka等组件实现高可用性和扩展性。Druid采用列式存储、并行计算和预计算等技术优化查询性能,支持离线和实时数据分析。尽管其存储成本较高且查询语言功能有限,但在大数据实时分析领域表现出色。
101 19
|
28天前
|
存储 SQL NoSQL
Doris 架构原理及核心特性详解
Doris 是百度内部孵化的OLAP项目,现已开源并广泛应用。它采用MPP架构、向量化执行引擎和列存储技术,提供高性能、易用性和实时数据处理能力。系统由FE(管理节点)和BE(计算与存储节点)组成,支持水平扩展和高可用性。Doris 适用于海量数据分析,尤其在电商、游戏等行业表现出色,但资源消耗较大,复杂查询优化有局限性,生态集成度有待提高。
83 15
|
1月前
|
搜索推荐 NoSQL Java
微服务架构设计与实践:用Spring Cloud实现抖音的推荐系统
本文基于Spring Cloud实现了一个简化的抖音推荐系统,涵盖用户行为管理、视频资源管理、个性化推荐和实时数据处理四大核心功能。通过Eureka进行服务注册与发现,使用Feign实现服务间调用,并借助Redis缓存用户画像,Kafka传递用户行为数据。文章详细介绍了项目搭建、服务创建及配置过程,包括用户服务、视频服务、推荐服务和数据处理服务的开发步骤。最后,通过业务测试验证了系统的功能,并引入Resilience4j实现服务降级,确保系统在部分服务故障时仍能正常运行。此示例旨在帮助读者理解微服务架构的设计思路与实践方法。
94 16
|
1月前
|
存储 消息中间件 小程序
转转平台IM系统架构设计与实践(一):整体架构设计
本文描述了转转IM为整个平台提供的支撑能力,给出了系统的整体架构设计,分析了系统架构的特性。
72 10
|
1月前
|
负载均衡 Serverless 持续交付
云端问道9期实践教学-省心省钱的云上Serverless高可用架构
详细介绍了云上Serverless高可用架构的一键部署流程
57 10
|
1月前
|
存储 人工智能 运维
面向AI的服务器计算软硬件架构实践和创新
阿里云在新一代通用计算服务器设计中,针对处理器核心数迅速增长(2024年超100核)、超多核心带来的业务和硬件挑战、网络IO与CPU性能增速不匹配、服务器物理机型复杂等问题,推出了磐久F系列通用计算服务器。该系列服务器采用单路设计减少爆炸半径,优化散热支持600瓦TDP,并实现CIPU节点比例灵活配比及部件模块化可插拔设计,提升运维效率和客户响应速度。此外,还介绍了面向AI的服务器架构挑战与软硬件结合创新,包括内存墙问题、板级工程能力挑战以及AI Infra 2.0服务器的开放架构特点。最后,探讨了大模型高效推理中的显存优化和量化压缩技术,旨在降低部署成本并提高系统效率。

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版