我应该如何用PostgreSQL实际实现记录历史表?

时间:2015-02-10 14:32:09

标签: postgresql database-design

我想为现有应用程序中的记录添加修订,该应用程序将数据存储在PostgreSQL数据库中。我读过有关策略,例如在this questionthis questionthis blog post

我认为创建第二个很少被查询的历史表的方法效果最好。但是我确实有一些实际问题。让我们说这是我的表我想将修订控制添加到:

create table people(
  id serial not null primary key,
  name varchar(255) not null
);

对于这个非常简单的表,我的历史表可能如下所示:

create table people_history(
  peopleId int not null references people(id) on delete cascade on update restrict,
  revision int not null,
  revisionTimestamp timestamptz not null default current_timestamp,
  name character varying(255) not null,
  primary key(peopleId, revision)
);

这带来了第一个问题:

  • 如何生成修订号?

当然,我可以创建一个序列,从中我可以轻松地请求修订号。然而,由于许多人共享相同的序列,因此每个人的修订版本之间会留下很大的差距,如果修订版数字是递增数字而没有人为差距,则会感觉更自然。 所以我很想通过select max(revision)+1 from ... where peopleId=...找到我的修订号。但是,如果两个线程要求下一个修订号并尝试插入,则可能导致竞争条件。我不太可能承认这一点(特别是在我的情况下,只有少数更新发生)并且不会导致数据损坏,因为这将是一个重复的主键,从而导致事务回滚,但它也不是很好。我想知道是否有更漂亮的解决方案。

  • 如何将数据插入历史记录表?

可以想到两种方法:手动更新主表或使用触发器的每个语句。触发器听起来不那么容易出错,因为我不太可能忘记某个地方的查询。但是,我无法准确地与应用程序通信创建了哪个版本号,是吗?所以如果我想创建几个这样的事件表:

create table peopleUserEditEvent (
  poepleId int not null,
  revision int not null,
  userId int not null references users(id) on delete set null on update restrict,
  comment text not null default '',
  primary key(paopleId, revision),
  foreign key (peopleId, revision) references people_history
);

列出了一些修订的元数据,解释了修订的原因。在这种情况下,具有特定ID的用户编辑了数据并可能提供了评论。

在另一个案例(和另一个事件表)中,cronjob可能已经更改了某些内容并记录了可能没有userId且没有注释但其他元数据的事件。

要添加这些事件数据,我需要修订版ID和如果修订版ID是由触发器创建的,那么很难找到(或者有实际的方法吗?)。

1 个答案:

答案 0 :(得分:0)

好吧,你需要一个复制策略用于你拥有的所有表和列,你可以创建一个表来维护所有的更改,并在你发出UPDATE INSERT或DELETE语句的任何时候插入,也许用这个framwork的例子,idempiere changelog可以帮助你

CREATE TABLE ad_changelog (
  ad_changelog_id NUMERIC(10,0) NOT NULL,
  ad_session_id NUMERIC(10,0) NOT NULL,
  ad_table_id NUMERIC(10,0) NOT NULL,
  ad_column_id NUMERIC(10,0) NOT NULL,
  isactive CHAR(1) DEFAULT 'Y'::bpchar NOT NULL,
  created TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
  createdby NUMERIC(10,0) NOT NULL,
  updated TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
  updatedby NUMERIC(10,0) NOT NULL,
  record_id NUMERIC(10,0) NOT NULL,
  oldvalue VARCHAR(2000),
  newvalue VARCHAR(2000),
  undo CHAR(1),
  redo CHAR(1),
  iscustomization CHAR(1) DEFAULT 'N'::bpchar NOT NULL,
  description VARCHAR(255),
  ad_changelog_uu VARCHAR(36) DEFAULT NULL::character varying,
  CONSTRAINT adcolumn_adchangelog FOREIGN KEY (ad_column_id)
    REFERENCES adempiere.ad_column(ad_column_id)
    MATCH PARTIAL
    ON DELETE CASCADE
    ON UPDATE NO ACTION
    DEFERRABLE
    INITIALLY DEFERRED,
  CONSTRAINT adsession_adchangelog FOREIGN KEY (ad_session_id)
    REFERENCES adempiere.ad_session(ad_session_id)
    MATCH PARTIAL
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
    DEFERRABLE
    INITIALLY DEFERRED,
  CONSTRAINT adtable_adchangelog FOREIGN KEY (ad_table_id)
    REFERENCES adempiere.ad_table(ad_table_id)
    MATCH PARTIAL
    ON DELETE CASCADE
    ON UPDATE NO ACTION
    DEFERRABLE
    INITIALLY DEFERRED
) 
WITH (oids = false);

CREATE INDEX ad_changelog_speed ON adempiere.ad_changelog
  USING btree (ad_table_id, record_id);

CREATE UNIQUE INDEX ad_changelog_uu_idx ON adempiere.ad_changelog
  USING btree (ad_changelog_uu COLLATE pg_catalog."default");