PostgreSQL 强大的多层表继承--及其在海量数据分类按月分区存储中的应用

http://blog.csdn.net/goldenhawking


    最近发现大家越来越关注 PostgreSQL了。2008年以来,通过对PostgreSQL的实际使用,发现其对象-关系数据库机制对现实问题非常有帮助。在多重表继承下,对上亿条不同类别的数据条目进行按型号、按月份双层分区管理,既可在总表查阅所有条目的共有字段,也可在各类型字表查询附加字段,非常高效。下面把这种分区机制介绍如下!

      实验平台:PostgreSQL 9.1

      实验背景:

      假设有N种数据收集设备,分别叫做 machine1, machine2...machineN, 各类收集设备从传感器上采集的数据各不相同。但是他们都包括3个共有属性:1、采集时刻 2、一个电压值 3、机器的ID。       这些机器源源不断的从各个传感器收集信息,每类机器还有各自不同的附加数据。比如,machine1有当前最大单元数、当前已使用单元数两个属性。Machine2有前端传感器的ID和取值。数据量约100万条/天,要求数据库容纳至少5年的数据。

     设计原则:

      由于采集的频率高,每天会有上百万条数据存入,为了考虑缩小索引的规模,提高检索效率,采用按月分区存储。由于各类机器的字段各有区别,使得我们必须设计不同的表结构, 分别存储各类数据。由于要求能够统一检索基本信息、按需检索额外信息,我们采用PostgreSQL的表继承,首先按照机器类型分类,而后各类型机器内按照月份分类。

     数据库结构:

    全局ID 序列:

  1.     CREATE SEQUENCE serial_id_seq  
  2.   INCREMENT 1  
  3.   MINVALUE 1  
  4.   MAXVALUE 9223372036854775807  
  5.   START 1  
  6.   CACHE 1;  
  7. ALTER TABLE serial_id_seq  
  8.   OWNER TO postgres;  

  该序列用于保持全局ID的唯一性。PostgreSQL各个继承表中的主键约束仅仅限于本表,在不想通过检查条件确保唯一的情况下,可以通过触发器手工从序列获取新的值,以及限制用户修改ID来保证唯一。基本表(爷爷表),承载了所有机器的共同属性
  1. CREATE TABLE base_table  
  2. (  
  3.   id bigint NOT NULL,  
  4.   dvalue double precision,  
  5.   sample_time timestamp with time zone,  
  6.   machine_code character varying(32),  
  7.   CONSTRAINT pk_base_table_id PRIMARY KEY (id )  
  8. )  
  9. WITH (  
  10.   OIDS=FALSE  
  11. );  
  12. ALTER TABLE base_table  
  13.   OWNER TO postgres;  
  14.   
  15. CREATE INDEX idx_sample_time  
  16.   ON base_table  
  17.   USING btree  
  18.   (sample_time );  

下面为机器类型1创建按类型分区子表(爸爸表)
  1. CREATE TABLE base_table_machine1  
  2. (  
  3.   max_res integer,  
  4.   curr_res integer,  
  5.   CONSTRAINT pk_base_table_machine1 PRIMARY KEY (id )  
  6. )  
  7. INHERITS (base_table)  
  8. WITH (  
  9.   OIDS=FALSE  
  10. );  
  11. ALTER TABLE base_table_machine1  
  12.   OWNER TO postgres;  
  13.   
  14.   
  15. CREATE INDEX idx_base_table_machine1_sample_time  
  16.   ON base_table_machine1  
  17.   USING btree  
  18.   (sample_time );  

同样,为机器2创建按类型分区子表
  1. CREATE TABLE base_table_machine2  
  2. (  
  3.   manu_id character varying(16),  
  4.   manu_value character varying(16),  
  5.   CONSTRAINT pk_base_table_machine2 PRIMARY KEY (id )  
  6. )  
  7. INHERITS (base_table)  
  8. WITH (  
  9.   OIDS=FALSE  
  10. );  
  11. ALTER TABLE base_table_machine2  
  12.   OWNER TO postgres;  
  13.   
  14. CREATE INDEX idx_base_table_machine2_sample_time  
  15.   ON base_table_machine2  
  16.   USING btree  
  17.   (sample_time );  

其他机器不再赘述。创建完后,我们开始写创建按月分区表的触发器(儿子表)。按月分区会判断每次插入的数据的时刻,按照月份放到分区表中。如果分区表不存在,则自动创建。这里给出机器1、机器2 的触发器
  1. -- Function: on_insert_base_table_machine1()  
  2.   
  3. -- DROP FUNCTION on_insert_base_table_machine1();  
  4.   
  5. CREATE OR REPLACE FUNCTION on_insert_base_table_machine1()  
  6.   RETURNS trigger AS  
  7. $BODY$  
  8. DECLARE   
  9. --Variable Hold subtable name  
  10. str_sub_tablename varchar;  
  11. --Variable Hold year\month info with timestamle  
  12. str_sub_sample_time varchar;  
  13. str_sql_cmd varchar;  
  14. str_sub_checkval varchar;  
  15. BEGIN  
  16.     --The triggle func will be exectued only when BEFORE INSERT  
  17.     IF TG_OP <> 'INSERT' OR TG_TABLE_NAME <>'base_table_machine1' OR TG_WHEN <> 'BEFORE' THEN  
  18.         RETURN NULL;  
  19.     END IF;  
  20.     --Generate Table Name  
  21.     str_sub_sample_time = date_part('year',NEW.sample_time)::varchar || '_' ||   
  22.         CASE WHEN date_part('month',NEW.sample_time) <10 THEN '0' ELSE '' END  
  23.         ||date_part('month',NEW.sample_time)::varchar;  
  24.     str_sub_tablename = 'machine1_' || str_sub_sample_time;  
  25.     --Check if table not created  
  26.     select * from pg_tables where schemaname = 'public' and tablename=str_sub_tablename   
  27.         into str_sql_cmd;  
  28.     IF NOT FOUND THEN  
  29.         --Create table Cmd  
  30.         str_sql_cmd = '  
  31.             CREATE TABLE '||str_sub_tablename||'   
  32.             (  
  33.                 CONSTRAINT pk_'|| str_sub_tablename||' PRIMARY KEY (id ),  
  34.                 CONSTRAINT chk_'|| str_sub_tablename||'  
  35.                  CHECK(date_part(''year''::text, sample_time) = '||  
  36.                  date_part('year',NEW.sample_time)::varchar||  
  37.                  '::double precision AND   
  38.                  date_part(''month''::text, sample_time) = '||  
  39.                  date_part('month',NEW.sample_time)::varchar||'  
  40.                  )  
  41.             )  
  42.             INHERITS (base_table_machine1)  
  43.             WITH ( OIDS=FALSE );  
  44.             ALTER TABLE '||str_sub_tablename||' OWNER TO postgres;  
  45.             CREATE INDEX idx_'|| str_sub_tablename||'_sample_time  
  46.                 ON '|| str_sub_tablename||'  
  47.                 USING btree (sample_time );  
  48.             ';  
  49.         EXECUTE str_sql_cmd;  
  50.     END IF;  
  51.     --insert Data  
  52.     str_sql_cmd = 'INSERT INTO '||str_sub_tablename||'   
  53.      ( id,dvalue,sample_time,machine_code,max_res,curr_res) VALUES (  
  54.      nextval(''serial_id_seq''),$1,$2,$3,$4,$5);  
  55.     ';  
  56.     EXECUTE str_sql_cmd USING  
  57.         NEW.dvalue,  
  58.         NEW.sample_time,  
  59.         NEW.machine_code,  
  60.         NEW.max_res,  
  61.         NEW.curr_res;  
  62.     --return null because main table does not really contain data  
  63.     return NULL;  
  64. END;  
  65.   
  66.     $BODY$  
  67.   LANGUAGE plpgsql VOLATILE  
  68.   COST 100;  
  69. ALTER FUNCTION on_insert_base_table_machine1()  
  70.   OWNER TO postgres;  

  1. -- Function: on_insert_base_table_machine2()  
  2.   
  3. -- DROP FUNCTION on_insert_base_table_machine2();  
  4.   
  5. CREATE OR REPLACE FUNCTION on_insert_base_table_machine2()  
  6.   RETURNS trigger AS  
  7. $BODY$  
  8. DECLARE   
  9. --Variable Hold subtable name  
  10. str_sub_tablename varchar;  
  11. --Variable Hold year\month info with timestamle  
  12. str_sub_sample_time varchar;  
  13. str_sql_cmd varchar;  
  14. str_sub_checkval varchar;  
  15. BEGIN  
  16.     --The triggle func will be exectued only when BEFORE INSERT  
  17.     IF TG_OP <> 'INSERT' OR TG_TABLE_NAME <>'base_table_machine2' OR TG_WHEN <> 'BEFORE' THEN  
  18.         RETURN NULL;  
  19.     END IF;  
  20.     --Generate Table Name  
  21.     str_sub_sample_time = date_part('year',NEW.sample_time)::varchar || '_' ||   
  22.         CASE WHEN date_part('month',NEW.sample_time) <10 THEN '0' ELSE '' END  
  23.         ||date_part('month',NEW.sample_time)::varchar;  
  24.     str_sub_tablename = 'machine2_' || str_sub_sample_time;  
  25.     --Check if table not created  
  26.     select * from pg_tables where schemaname = 'public' and tablename=str_sub_tablename   
  27.         into str_sql_cmd;  
  28.     IF NOT FOUND THEN  
  29.         --Create table Cmd  
  30.         str_sql_cmd = '  
  31.             CREATE TABLE '||str_sub_tablename||'   
  32.             (  
  33.                 CONSTRAINT pk_'|| str_sub_tablename||' PRIMARY KEY (id ),  
  34.                 CONSTRAINT chk_'|| str_sub_tablename||'  
  35.                  CHECK(date_part(''year''::text, sample_time) = '||  
  36.                  date_part('year',NEW.sample_time)::varchar||  
  37.                  '::double precision AND   
  38.                  date_part(''month''::text, sample_time) = '||  
  39.                  date_part('month',NEW.sample_time)::varchar||'  
  40.                  )  
  41.             )  
  42.             INHERITS (base_table_machine2)  
  43.             WITH ( OIDS=FALSE );  
  44.             ALTER TABLE '||str_sub_tablename||' OWNER TO postgres;  
  45.             CREATE INDEX idx_'|| str_sub_tablename||'_sample_time  
  46.                 ON '|| str_sub_tablename||'  
  47.                 USING btree (sample_time );  
  48.             ';  
  49.         EXECUTE str_sql_cmd;  
  50.     END IF;  
  51.     --insert Data  
  52.     str_sql_cmd = 'INSERT INTO '||str_sub_tablename||'   
  53.      ( id,dvalue,sample_time,machine_code,manu_id,manu_value) VALUES (  
  54.      nextval(''serial_id_seq''),$1,$2,$3,$4,$5);  
  55.     ';  
  56.     EXECUTE str_sql_cmd USING  
  57.         NEW.dvalue,  
  58.         NEW.sample_time,  
  59.         NEW.machine_code,  
  60.         NEW.manu_id,  
  61.         NEW.manu_value;  
  62.     --return null because main table does not really contain data  
  63.     return NULL;  
  64. END;  
  65.   
  66.     $BODY$  
  67.   LANGUAGE plpgsql VOLATILE  
  68.   COST 100;  
  69. ALTER FUNCTION on_insert_base_table_machine2()  
  70.   OWNER TO postgres;  

最后,为各个爸爸表设置触发器
  1. CREATE TRIGGER triggle_on_insert_machine1  
  2.   BEFORE INSERT  
  3.   ON base_table_machine1  
  4.   FOR EACH ROW  
  5.   EXECUTE PROCEDURE on_insert_base_table_machine1();  

  1. CREATE TRIGGER triggle_machine2  
  2.   BEFORE INSERT  
  3.   ON base_table_machine2  
  4.   FOR EACH ROW  
  5.   EXECUTE PROCEDURE on_insert_base_table_machine2();  

到此为止,我们可以分别向各个爸爸表(按类型分区表)插入数据,而后通过爷爷表(总表)检索基本信息,通过爸爸表检索详细信息。对总表的操作会遍历反馈到所有子表,试图利用子表的索引进行查询。由于按月存储,插入工作只限于本月,所以检索历史数据效率很高。

当然了,这只是简单的实验,实际字段要比上述字段复杂很多。PostgreSQL的对象-关系数据库对解决上述问题非常有帮助,也全面的运用到我公司的各个环节,达到工业化标准的系统非常稳定,尽管设置了备份,但4年来从未真正用到。我们目前使用 16核心机架服务器,8GB内存,Ubuntu 12.04 LTS,优化配置(Postgresql.conf) 采用设置共享段shared_buffers 512MB, work_mem 32MB,维护maintenance_work_mem  512MB,checkpoint_segments = 16,获得了稳定而持久的生产力提升。

测试:

插入4条数据

  1. insert into base_table_machine1 (dvalue,sample_time,machine_code,max_res,curr_res) values (22.17273,'2012-06-01 11:22:11','SC3010-192.168.1.12',1,2);  
  2. insert into base_table_machine1 (dvalue,sample_time,machine_code,max_res,curr_res) values (12.8273,'2012-07-12 10:23:01','SC3010-192.168.1.14',1,2);  
  3. insert into base_table_machine2 (dvalue,sample_time,machine_code,manu_id,manu_value) values (4412.1928,'2011-01-21 02:08:34','PK937-192.168.1.113','TP1','E54DF');  
  4. insert into base_table_machine2 (dvalue,sample_time,machine_code,manu_id,manu_value) values (4412.1928,'2011-12-31 04:21:31','PK937-192.168.1.112','TP2','CB67D');  

看看 select  语句的结果

select * from base_table;

select * from base_table_machine2;

select * from base_table_machine1;

explain select * from base_table where sample_time >='2012-06-21 00:00:00' and sample_time <='2012-07-21 00:00:00';

自动使用索引关联到每个子表。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值