如何从Firebird DB中读取所有“ last_changed”记录?

时间:2019-11-27 14:04:01

标签: sql triggers firebird

我的问题有点棘手,因为这主要是一个逻辑问题。
我试图通过将所有内容读取到内存中(但仅读取那些记录)来优化我的应用程序速度,这些记录自“ last read” =上次加载记录的最大时间戳以来就发生了变化。

FirebirdSQL数据库引擎不允许直接在“触发后”中更新字段,因此显然使用“更新或插入之前”触发器来更新字段new.last_changed = current_timestamp;

问题:

事实证明,这是一种完全错误的方法,因为这些触发器会在交易开始时触发!
因此,如果有一个事务比另一个事务花费更多的时间,则保存的“最后更改时间”将比两次之间触发并完成的短暂突发事务要短。
1. tr.: 13:00:01.400 .............................Commit <<该记录将被跳过!
2. tr.: 13:00.01.500......Commit <<这里将进行数据读取。
下一次读取将是>= 13:00.01.500

我尝试过:

重写所有触发器,因此它们会在之后触发并调用UPDATE orders SET ... << ,但这会导致循环的,自调用的无尽事件。
SET_CONTEXT lock是否会干扰多行更新和嵌套触发器?
 (如果在同一个事务中运行多个更新,我认为这种方法很好不太可能。)

所有这些通用解决方案是什么?

Edit1:

我想做的是仅读取 那些自上次读取以来实际上已更改的数据库记录。为此,我需要引擎在提交后更新记录。 (不是在“中间”。)
此触发器不好,因为它将在更改时触发(而不是在提交后触发):

alter trigger SYNC_ORDERS active after insert or update position 999 AS
declare variable N timestamp; 
begin
  N = cast('NOW' as timestamp);
  if (new.last_changed <> :N) then
    update ORDERS set last_changed= :N where ID=new.ID;
end

然后从应用程序中执行:

Query1.SQL.Text := 'SELECT * FROM orders WHERE last_changed >= ' + DateTimeToStr( latest_record );  
Query1.Open;  
 latest_record := Query1.FieldByName('last_changed').asDateTime;   

..此代码将仅列出第二个事务中提交的记录(较早),而不列出第一个运行时间更长的事务(稍后提交)。

Edit2:

似乎我和here...有相同的问题,但特别是对于FirebirdSQL。
那里确实没有任何好的解决方案,但是给了我一个主意:
-如果我创建一个额外的表并在每个表的5分钟之前记录更改,该怎么办?
-在执行每个SQL查询之前,首先,我会要求该表中的所有更改(通过ID增长排序)! -删除超过23小时的行

ID  TableID  Changed
===========================
1   5   2019.11.27 19:36:21
2   5   2019.11.27 19:31:19

Edit3:

正如Arioch已经建议的那样,一种解决方案是:

  • 在每个BEFORE INSERT OR UPDATE上创建一个“记录器表” 由每个表触发
  • 并更新其“ last_changed”序列 通过ON TRANSACTION COMMIT触发器

但是,不会...

更好的方法?

  • 向每个表添加1-1 last_sequence INT64 DEFAULT NULL
  • 创建全局生成器LAST_GEN
  • ON TRANSACTION COMMIT触发器内使用gen_id(LAST_GEN,1)更新每个表的每个NULL行
  • 在每个BEFORE INSERT OR UPDATE触发器上再次将其设置为NULL

因此,基本上将记录的last_sequence列切换为:
 NULL > 1 > NULL > 34 ...每次对其进行修改。
这样我:

  • 不必用日志数据填充数据库,
  • ,我可以直接使用WHERE last_sequence>1;查询表。
  • 不需要先查询“记录器表”。

我只是害怕:如果ON TRANSACTION COMMIT触发器尝试更新last_sequence字段,第二个事务的ON之前触发器将锁定记录,会发生什么情况(另一张桌子的)?
这会发生吗?

1 个答案:

答案 0 :(得分:0)

最终的解决方案基于以下想法:

  1. 每个表的BEFORE INSERT OR UPDATE触发器可以推迟事务处理时间:RDB$SET_CONTEXT('USER_TRANSACTION', 'table31', current_timestamp);
  2. 如果全局ON TRANSACTION COMMIT触发器收到这样的上下文,则可以将其+时间插入“日志表”中。
  3. 通过仅记录“大时差”(例如> = 1分钟)以减少记录量,它甚至可以处理“夏令时更改”和“间隔”。)
  4. 存储过程可以简化和加快每个查询的“ LAST_QUERY_TIME”的计算。

示例:

1。)

create trigger ORDERS_BI active before insert or update position 0 AS
BEGIN
  IF (NEW.ID IS NULL) THEN
    NEW.ID = GEN_ID(GEN_ORDERS,1);
  RDB$SET_CONTEXT('USER_TRANSACTION', 'orders_table', current_timestamp);  
END

2,3。)

create trigger TRG_SYNC_AFTER_COMMIT ACTIVE ON transaction commit POSITION 1 as 
  declare variable N TIMESTAMP;
  declare variable T VARCHAR(255);
begin
  N = cast('NOW' as timestamp);
  T = RDB$GET_CONTEXT('USER_TRANSACTION', 'orders_table');

  if (:T is not null) then begin
    if (:N < :T) then T = :N; --system time changed eg.: daylight saving" -1 hour
    if (datediff(second from :T to :N) > 60 ) then --more than 1min. passed
      insert into "SYNC_PAST_TIMES" (ID, TABLE_NUMBER, TRG_START, SYNC_TIME, C_USER)
        values (GEN_ID(GEN_SYNC_PAST_TIMES, 1), 31, cast(:T as timestamp), :N, CURRENT_USER);
  end;  

  T = RDB$GET_CONTEXT('USER_TRANSACTION', 'details_table');
-- other tables ...

  when any do EXIT;
end