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

前端之家收集整理的这篇文章主要介绍了PostgreSQL 强大的多层表继承--及其在海量数据分类按月分区存储中的应用前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
最近发现大家越来越关注 Postgresql了。2008年以来,通过对Postgresql的实际使用,发现其对象-关系数据库机制对现实问题非常有帮助。在多重表继承下,对上亿条不同类别的数据条目进行按型号、按月份双层分区管理,既可在总表查阅所有条目的共有字段,也可在各类型字表查询附加字段,非常高效。下面把这种分区机制介绍如下!

实验平台:Postgresql 9.1

实验背景:

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

设计原则:

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

数据库结构:

全局ID 序列:

  1. CREATESEQUENCEserial_id_seq
  2. INCREMENT1
  3. MINVALUE1
  4. MAXVALUE9223372036854775807
  5. START1
  6. CACHE1;
  7. ALTERTABLEserial_id_seq
  8. OWNERTOpostgres;

该序列用于保持全局ID的唯一性。Postgresql各个继承表中的主键约束仅仅限于本表,在不想通过检查条件确保唯一的情况下,可以通过触发器手工从序列获取新的值,以及限制用户修改ID来保证唯一。基本表(爷爷表),承载了所有机器的共同属性
  1. CREATETABLEbase_table
  2. (
  3. idbigintNOTNULL,
  4. dvaluedoubleprecision,
  5. sample_timetimestampwithtimezone,
  6. machine_codecharactervarying(32),
  7. CONSTRAINTpk_base_table_idPRIMARYKEY(id)
  8. )
  9. WITH(
  10. OIDS=FALSE
  11. );
  12. ALTERTABLEbase_table
  13. OWNERTOpostgres;
  14. CREATEINDEXidx_sample_time
  15. ONbase_table
  16. USINGbtree
  17. (sample_time);

下面为机器类型1创建按类型分区子表(爸爸表)
  1. CREATETABLEbase_table_machine1
  2. (
  3. max_resinteger,
  4. curr_resinteger,
  5. CONSTRAINTpk_base_table_machine1PRIMARYKEY(id)
  6. )
  7. INHERITS(base_table)
  8. WITH(
  9. OIDS=FALSE
  10. );
  11. ALTERTABLEbase_table_machine1
  12. OWNERTOpostgres;
  13. CREATEINDEXidx_base_table_machine1_sample_time
  14. ONbase_table_machine1
  15. USINGbtree
  16. (sample_time);

同样,为机器2创建按类型分区子表
  1. CREATETABLEbase_table_machine2
  2. (
  3. manu_idcharactervarying(16),
  4. manu_valuecharactervarying(16),
  5. CONSTRAINTpk_base_table_machine2PRIMARYKEY(id)
  6. )
  7. INHERITS(base_table)
  8. WITH(
  9. OIDS=FALSE
  10. );
  11. ALTERTABLEbase_table_machine2
  12. OWNERTOpostgres;
  13. CREATEINDEXidx_base_table_machine2_sample_time
  14. ONbase_table_machine2
  15. USINGbtree
  16. (sample_time);

其他机器不再赘述。创建完后,我们开始写创建按月分区表的触发器(儿子表)。按月分区会判断每次插入的数据的时刻,按照月份放到分区表中。如果分区表不存在,则自动创建。这里给出机器1、机器2 的触发器
  1. --Function:on_insert_base_table_machine1()
  2. --DROPFUNCTIONon_insert_base_table_machine1();
  3. CREATEORREPLACEFUNCTIONon_insert_base_table_machine1()
  4. RETURNStriggerAS
  5. $BODY$
  6. DECLARE
  7. --VariableHoldsubtablename
  8. str_sub_tablenamevarchar;
  9. --VariableHoldyear\monthinfowithtimestamle
  10. str_sub_sample_timevarchar;
  11. str_sql_cmdvarchar;
  12. str_sub_checkvalvarchar;
  13. BEGIN
  14. --ThetrigglefuncwillbeexectuedonlywhenBEFOREINSERT
  15. IFTG_OP<>'INSERT'ORTG_TABLE_NAME<>'base_table_machine1'ORTG_WHEN<>'BEFORE'THEN
  16. RETURNNULL;
  17. ENDIF;
  18. --GenerateTableName
  19. str_sub_sample_time=date_part('year',NEW.sample_time)::varchar||'_'||
  20. CASEWHENdate_part('month',NEW.sample_time)<10THEN'0'ELSE''END
  21. ||date_part('month',NEW.sample_time)::varchar;
  22. str_sub_tablename='machine1_'||str_sub_sample_time;
  23. --Checkiftablenotcreated
  24. select*frompg_tableswhereschemaname='public'andtablename=str_sub_tablename
  25. intostr_sql_cmd;
  26. IFNOTFOUNDTHEN
  27. --CreatetableCmd
  28. str_sql_cmd='
  29. CREATETABLE'||str_sub_tablename||'
  30. (
  31. CONSTRAINTpk_'||str_sub_tablename||'PRIMARYKEY(id),
  32. CONSTRAINTchk_'||str_sub_tablename||'
  33. CHECK(date_part(''year''::text,sample_time)='||
  34. date_part('year',NEW.sample_time)::varchar||
  35. '::doubleprecisionAND
  36. date_part(''month''::text,sample_time)='||
  37. date_part('month',NEW.sample_time)::varchar||'
  38. )
  39. )
  40. INHERITS(base_table_machine1)
  41. WITH(OIDS=FALSE);
  42. ALTERTABLE'||str_sub_tablename||'OWNERTOpostgres;
  43. CREATEINDEXidx_'||str_sub_tablename||'_sample_time
  44. ON'||str_sub_tablename||'
  45. USINGbtree(sample_time);
  46. ';
  47. EXECUTEstr_sql_cmd;
  48. ENDIF;
  49. --insertData
  50. str_sql_cmd='INSERTINTO'||str_sub_tablename||'
  51. (id,dvalue,sample_time,machine_code,max_res,curr_res)VALUES(
  52. nextval(''serial_id_seq''),$1,$2,$3,$4,$5);
  53. ';
  54. EXECUTEstr_sql_cmdUSING
  55. NEW.dvalue,
  56. NEW.sample_time,
  57. NEW.machine_code,
  58. NEW.max_res,
  59. NEW.curr_res;
  60. --returnnullbecausemaintabledoesnotreallycontaindata
  61. returnNULL;
  62. END;
  63. $BODY$
  64. LANGUAGEplpgsqlVOLATILE
  65. COST100;
  66. ALTERFUNCTIONon_insert_base_table_machine1()
  67. OWNERTOpostgres;

  1. --Function:on_insert_base_table_machine2()
  2. --DROPFUNCTIONon_insert_base_table_machine2();
  3. CREATEORREPLACEFUNCTIONon_insert_base_table_machine2()
  4. RETURNStriggerAS
  5. $BODY$
  6. DECLARE
  7. --VariableHoldsubtablename
  8. str_sub_tablenamevarchar;
  9. --VariableHoldyear\monthinfowithtimestamle
  10. str_sub_sample_timevarchar;
  11. str_sql_cmdvarchar;
  12. str_sub_checkvalvarchar;
  13. BEGIN
  14. --ThetrigglefuncwillbeexectuedonlywhenBEFOREINSERT
  15. IFTG_OP<>'INSERT'ORTG_TABLE_NAME<>'base_table_machine2'ORTG_WHEN<>'BEFORE'THEN
  16. RETURNNULL;
  17. ENDIF;
  18. --GenerateTableName
  19. str_sub_sample_time=date_part('year',NEW.sample_time)::varchar;
  20. str_sub_tablename='machine2_'||str_sub_sample_time;
  21. --Checkiftablenotcreated
  22. select*frompg_tableswhereschemaname='public'andtablename=str_sub_tablename
  23. intostr_sql_cmd;
  24. IFNOTFOUNDTHEN
  25. --CreatetableCmd
  26. str_sql_cmd='
  27. CREATETABLE'||str_sub_tablename||'
  28. (
  29. CONSTRAINTpk_'||str_sub_tablename||'PRIMARYKEY(id),NEW.sample_time)::varchar||'
  30. )
  31. )
  32. INHERITS(base_table_machine2)
  33. WITH(OIDS=FALSE);
  34. ALTERTABLE'||str_sub_tablename||'OWNERTOpostgres;
  35. CREATEINDEXidx_'||str_sub_tablename||'_sample_time
  36. ON'||str_sub_tablename||'
  37. USINGbtree(sample_time);
  38. ';
  39. EXECUTEstr_sql_cmd;
  40. ENDIF;
  41. --insertData
  42. str_sql_cmd='INSERTINTO'||str_sub_tablename||'
  43. (id,manu_id,manu_value)VALUES(
  44. nextval(''serial_id_seq''),
  45. NEW.manu_id,
  46. NEW.manu_value;
  47. --returnnullbecausemaintabledoesnotreallycontaindata
  48. returnNULL;
  49. END;
  50. $BODY$
  51. LANGUAGEplpgsqlVOLATILE
  52. COST100;
  53. ALTERFUNCTIONon_insert_base_table_machine2()
  54. OWNERTOpostgres;

最后,为各个爸爸表设置触发器
  1. CREATETRIGGERtriggle_on_insert_machine1
  2. BEFOREINSERT
  3. ONbase_table_machine1
  4. FOREACHROW
  5. EXECUTEPROCEDUREon_insert_base_table_machine1();

  1. CREATETRIGGERtriggle_machine2
  2. BEFOREINSERT
  3. ONbase_table_machine2
  4. FOREACHROW
  5. EXECUTEPROCEDUREon_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. insertintobase_table_machine1(dvalue,curr_res)values(22.17273,'2012-06-0111:22:11','SC3010-192.168.1.12',1,2);
  2. insertintobase_table_machine1(dvalue,curr_res)values(12.8273,'2012-07-1210:23:01','SC3010-192.168.1.14',2);
  3. insertintobase_table_machine2(dvalue,manu_value)values(4412.1928,'2011-01-2102:08:34','PK937-192.168.1.113','TP1','E54DF');
  4. insertintobase_table_machine2(dvalue,'2011-12-3104: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';

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

猜你在找的Postgre SQL相关文章