2019独角兽企业重金招聘Python工程师标准>>>
前言
PostgreSQL9.3开始推出postgres_fdw功能,9.5又实现了外部表可以继承的特性。那么我们就可以像实现分区表一样,利用"外部表继承+约束+规则"来实现数据库的sharding。
原理
传统的普通表之间的继承特性可以实现数据库级别的表分区,而外部表可以继承普通表的特性,则可以实现跨数据库,甚至跨物理节点的表分区。下图是实现数据库sharding的一个逻辑图。
应用
测试场景有4台机器,一台作为本地机用于管理访问外部节点分区,另外3台作为存储表分区的机器。
1. 先在3台外部节点机器上,创建3个表结构相同的普通表,并添加相关约束。
postgres&#61;# create table test_partition_127(id int check (id >&#61; 1 AND id <100), tt text, tm timestamp);
CREATE TABLE
postgres&#61;# create table test_partition_136(id int check (id >&#61; 100 AND id <200), tt text, tm timestamp);
CREATE TABLE
postgres&#61;# create table test_partition_144(id int check (id >&#61; 200 AND id <300), tt text, tm timestamp);
CREATE TABLE
表名后面的数字127,136,144表示节点IP的后三位。
2. 为了让本地机器可以访问外部节点&#xff0c;还需要对这3个外部节点的配置文件postgresql.conf&#xff0c;pg_hba.conf配置一下。
下面操作都是在本地机上进行。
3. 先创建postgres_fdw扩展。
postgres&#61;# create extension postgres_fdw;
CREATE EXTENSION
4. 创建可以映射到外部节点普通表的外部表。
postgres&#61;# CREATE SERVER server_remote_127 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host &#39;192.168.100.127&#39;, port &#39;5432&#39;, dbname &#39;postgres&#39;);
CREATE SERVER
postgres&#61;# CREATE USER MAPPING FOR postgres SERVER server_remote_127 OPTIONS(user &#39;highgo&#39;);
CREATE USER MAPPING
postgres&#61;# CREATE FOREIGN TABLE test_partition_foreign_127(id int, tt text, tm timestamp) SERVER server_remote_127 OPTIONS(schema_name &#39;public&#39;, table_name &#39;test_partition_127&#39;);
CREATE FOREIGN TABLE
postgres&#61;#
postgres&#61;# CREATE SERVER server_remote_136 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host &#39;192.168.100.136&#39;, port &#39;5432&#39;, dbname &#39;postgres&#39;);
CREATE SERVER
postgres&#61;# CREATE USER MAPPING FOR postgres SERVER server_remote_136 OPTIONS(user &#39;highgo&#39;);
CREATE USER MAPPING
postgres&#61;# CREATE FOREIGN TABLE test_partition_foreign_136(id int, tt text, tm timestamp) SERVER server_remote_136 OPTIONS(schema_name &#39;public&#39;, table_name &#39;test_partition_136&#39;);
CREATE FOREIGN TABLE
postgres&#61;#
postgres&#61;# CREATE SERVER server_remote_144 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host &#39;192.168.100.144&#39;, port &#39;5432&#39;, dbname &#39;postgres&#39;);
CREATE SERVER
postgres&#61;# CREATE USER MAPPING FOR postgres SERVER server_remote_144 OPTIONS(user &#39;highgo&#39;);
CREATE USER MAPPING
postgres&#61;# CREATE FOREIGN TABLE test_partition_foreign_144(id int, tt text, tm timestamp) SERVER server_remote_144 OPTIONS(schema_name &#39;public&#39;, table_name &#39;test_partition_144&#39;);
CREATE FOREIGN TABLE
5. 创建本地父表&#xff0c;并让上一步创建的外部表继承于父表。
postgres&#61;# create table test_master(id int, tt text, tm timestamp);
CREATE TABLE
postgres&#61;# alter table test_partition_foreign_127 inherit test_master;
ALTER TABLE
postgres&#61;# alter table test_partition_foreign_136 inherit test_master;
ALTER TABLE
postgres&#61;# alter table test_partition_foreign_144 inherit test_master;
ALTER TABLE
6. 查看外部表的信息。
postgres&#61;# \d test_partition_foreign_127引用的外部表 "public.test_partition_foreign_127"栏位 | 类型 | 修饰词 | FDW选项
------&#43;-----------------------------&#43;--------&#43;---------id | integer | | tt | text | | tm | timestamp without time zone | |
Server: server_remote_127
FDW Options: (schema_name &#39;public&#39;, table_name &#39;test_partition_127&#39;)
继承: test_masterpostgres&#61;# \d test_partition_foreign_136引用的外部表 "public.test_partition_foreign_136"栏位 | 类型 | 修饰词 | FDW选项
------&#43;-----------------------------&#43;--------&#43;---------id | integer | | tt | text | | tm | timestamp without time zone | |
Server: server_remote_136
FDW Options: (schema_name &#39;public&#39;, table_name &#39;test_partition_136&#39;)
继承: test_masterpostgres&#61;# \d test_partition_foreign_144引用的外部表 "public.test_partition_foreign_144"栏位 | 类型 | 修饰词 | FDW选项
------&#43;-----------------------------&#43;--------&#43;---------id | integer | | tt | text | | tm | timestamp without time zone | |
Server: server_remote_144
FDW Options: (schema_name &#39;public&#39;, table_name &#39;test_partition_144&#39;)
继承: test_master
外部表已经继承了父表test_master。
7. 对父表创建规则&#xff0c;以便向父表中插入数据时&#xff0c;数据能分发到外部表中。
postgres&#61;# CREATE OR REPLACE RULE insert_rule_test_partition_foreign_127 AS ON INSERT TO test_master WHERE id >&#61; 1 AND id <100 DO INSTEAD INSERT INTO test_partition_foreign_127 VALUES(NEW.*);
CREATE RULE
postgres&#61;#
postgres&#61;# CREATE OR REPLACE RULE insert_rule_test_partition_foreign_136 AS ON INSERT TO test_master WHERE id >&#61; 100 AND id <200 DO INSTEAD INSERT INTO test_partition_foreign_136 VALUES(NEW.*);
CREATE RULE
postgres&#61;#
postgres&#61;# CREATE OR REPLACE RULE insert_rule_test_partition_foreign_144 AS ON INSERT TO test_master WHERE id >&#61; 200 AND id <300 DO INSTEAD INSERT INTO test_partition_foreign_144 VALUES(NEW.*);
CREATE RULE
8. 向父表中插入数据测试。
postgres&#61;# insert into test_master select generate_series(1, 299), &#39;highgo&#39;, now();
INSERT 0 0
9. 查看select父表的执行计划。
postgres&#61;# explain analyze select * from test_master;QUERY PLAN ---------------------------------------------------------------------------------------------------------
---------------------------------Append (cost&#61;0.00..438.36 rows&#61;3613 width&#61;44) (actual time&#61;6.364..34.279 rows&#61;299 loops&#61;1)-> Seq Scan on test_master (cost&#61;0.00..0.00 rows&#61;1 width&#61;44) (actual time&#61;0.016..0.016 rows&#61;0 loops&#61;
1)-> Foreign Scan on test_partition_foreign_127 (cost&#61;100.00..146.12 rows&#61;1204 width&#61;44) (actual time&#61;
6.345..6.378 rows&#61;99 loops&#61;1)-> Foreign Scan on test_partition_foreign_136 (cost&#61;100.00..146.12 rows&#61;1204 width&#61;44) (actual time&#61;
21.258..21.707 rows&#61;100 loops&#61;1)-> Foreign Scan on test_partition_foreign_144 (cost&#61;100.00..146.12 rows&#61;1204 width&#61;44) (actual time&#61;
5.327..6.054 rows&#61;100 loops&#61;1)Planning time: 0.617 msExecution time: 40.256 ms
10. 查看3个节点的表的数据存储。
postgres&#61;# select * from test_partition_127 limit 5;id | tt | tm
----&#43;--------&#43;----------------------------1 | highgo | 2017-06-26 11:07:11.8169572 | highgo | 2017-06-26 11:07:11.8169573 | highgo | 2017-06-26 11:07:11.8169574 | highgo | 2017-06-26 11:07:11.8169575 | highgo | 2017-06-26 11:07:11.816957
(5 行记录)postgres&#61;# explain analyze select * from test_partition_127 ;QUERY PLAN
---------------------------------------------------------------------------------------------------------------Seq Scan on test_partition_127 (cost&#61;0.00..1.99 rows&#61;99 width&#61;19) (actual time&#61;0.026..0.107 rows&#61;99 loops&#61;1)Planning time: 0.094 msExecution time: 0.238 ms
(3 行记录)
postgres&#61;# select * from test_partition_136 limit 5;id | tt | tm
-----&#43;--------&#43;----------------------------100 | highgo | 2017-06-26 11:07:11.816957101 | highgo | 2017-06-26 11:07:11.816957102 | highgo | 2017-06-26 11:07:11.816957103 | highgo | 2017-06-26 11:07:11.816957104 | highgo | 2017-06-26 11:07:11.816957
(5 行记录)postgres&#61;# explain analyze select * from test_partition_136;QUERY PLAN
-----------------------------------------------------------------------------------------------------------------Seq Scan on test_partition_136 (cost&#61;0.00..2.00 rows&#61;100 width&#61;19) (actual time&#61;0.022..0.067 rows&#61;100 loops&#61;1)Planning time: 0.088 msExecution time: 0.152 ms
(3 行记录)
postgres&#61;# select * from test_partition_144 limit 5;id | tt | tm
-----&#43;--------&#43;----------------------------200 | highgo | 2017-06-26 11:07:11.816957201 | highgo | 2017-06-26 11:07:11.816957202 | highgo | 2017-06-26 11:07:11.816957203 | highgo | 2017-06-26 11:07:11.816957204 | highgo | 2017-06-26 11:07:11.816957
(5 行记录)postgres&#61;# explain analyze select * from test_partition_144;QUERY PLAN
-----------------------------------------------------------------------------------------------------------------Seq Scan on test_partition_144 (cost&#61;0.00..2.00 rows&#61;100 width&#61;19) (actual time&#61;0.039..0.144 rows&#61;100 loops&#61;1)Planning time: 0.083 msExecution time: 0.403 ms
(3 行记录)
插入的数据已经通过规则和外部表&#xff0c;分发到下面三个节点中去。
性能方面
写入方面&#xff0c;由于采用了规则&#xff0c;性能自然不会很好。这里重点说一下读的性能&#xff0c;对父表做一次条件检索。
postgres&#61;# explain analyze select * from test_master where id &#61; 123;QUERY PLAN ---------------------------------------------------------------------------------------------------------
--------------------------Append (cost&#61;0.00..375.51 rows&#61;19 width&#61;44) (actual time&#61;7.614..11.620 rows&#61;1 loops&#61;1)-> Seq Scan on test_master (cost&#61;0.00..0.00 rows&#61;1 width&#61;44) (actual time&#61;0.002..0.002 rows&#61;0 loops&#61;
1)Filter: (id &#61; 123)-> Foreign Scan on test_partition_foreign_127 (cost&#61;100.00..125.17 rows&#61;6 width&#61;44) (actual time&#61;3.3
59..3.359 rows&#61;0 loops&#61;1)-> Foreign Scan on test_partition_foreign_136 (cost&#61;100.00..125.17 rows&#61;6 width&#61;44) (actual time&#61;4.2
48..4.251 rows&#61;1 loops&#61;1)-> Foreign Scan on test_partition_foreign_144 (cost&#61;100.00..125.17 rows&#61;6 width&#61;44) (actual time&#61;4.0
00..4.000 rows&#61;0 loops&#61;1)Planning time: 0.464 msExecution time: 19.298 ms
可以看出&#xff0c;执行计划是对所有的外部表做的扫描&#xff0c;性能上肯定会大打折扣。传统的普通表实现的分区可以利用继承表的查询优化&#xff0c;对主表进行条件检索时&#xff0c;只对目标子分区进行顺序扫描。所以目前利用外部表实现的数据库的sharding&#xff0c;查询优化还需要做下优化工作。