如何从 Firebird DB 中读取所有 "last_changed" 条记录?
How to read all "last_changed" records from Firebird DB?
我的问题有点棘手,因为它主要是一个逻辑问题。
我试图通过将所有内容读入内存来优化我的应用程序速度,但只有那些记录发生了变化,因为 "last read" = 上次加载记录的最大时间戳。
FirebirdSQL 数据库引擎不允许直接更新 "After Trigger" 中的字段,所以它显然使用 "before update or insert" 触发器来更新字段 new.last_changed = current_timestamp;
问题:
事实证明,这是一个完全错误的方法,因为这些触发器在事务 start!
上触发
因此,如果有一个事务比另一个事务花费更多时间,则保存的 "last changed time" 将低于在两者之间触发和完成的短突发事务。
1. tr.: 13:00:01.400 .............................Commit
<< 这条记录将被跳过!
2. tr.: 13:00.01.500......Commit
<< 数据读取将发生在这里。
下一个阅读将是 >= 13:00.01.500
我试过:
重写所有触发器,因此它们会在 之后触发并调用 UPDATE orders SET ...
<< ,但这会导致循环、自调用无限事件。
SET_CONTEXT
lock 会干扰 多行 更新和 嵌套 触发器吗?
(如果 运行 在同一个事务中进行多次更新,我认为此方法没有任何工作的可能性 好 。)
所有这些的通用解决方案是什么?
编辑 1:
我想要发生的是只读取那些来自数据库的记录自上次读取以来实际上发生了变化。为此,我需要引擎在提交后更新记录。 (不是在那期间,"in the middle"。)
这个触发器不好,因为它会在更改时触发,(不是在提交之后):
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;
..此代码将仅列出在第二个事务(较早)中提交的记录,而不会列出第一个、更长的 运行 事务(较晚提交)。
编辑2:
看来我和here...有同样的问题,但是专门针对FirebirdSQL.
那里真的没有什么好的解决方案,但给了我一个想法:
- 如果我创建一个额外的 table 并在每个 table 之前 5 分钟内记录更改怎么办?
- 在每个 SQL 查询之前,首先我会询问 table 中的任何更改,通过 ID grow!
排序
- 删除超过 23 小时的行
ID TableID Changed
===========================
1 5 2019.11.27 19:36:21
2 5 2019.11.27 19:31:19
编辑 3:
正如 Arioch 已经建议的那样,一种解决方案是:
- 创建一个 "logger table" 填充每个
BEFORE INSERT OR UPDATE
由每个 table 触发
- 并更新它的"last_changed"序列
通过
ON TRANSACTION COMMIT
触发器
但是,不会...
更好的方法?:
- 向每个 table
添加 1-1 last_sequence INT64 DEFAULT NULL
列
- 创建全局生成器
LAST_GEN
- 在
ON TRANSACTION COMMIT
触发器 中使用 gen_id(LAST_GEN,1) 更新每个 table 的每个 NULL 行
- 在每个
BEFORE INSERT OR UPDATE
触发时再次设置为 NULL
所以基本上将记录的 last_sequence
列切换为:
NULL > 1 > NULL > 34
...每次修改。
这样我:
- 不必用日志数据填充数据库,
- 我可以直接用
WHERE last_sequence>1;
查询tables。
- 不需要先预查询"logger table"。
我只是害怕:会发生什么,如果 ON TRANSACTION COMMIT
触发器试图更新 last_sequence
字段,而 第二个事务在 ON BEFORE触发器正在锁定记录(另一个 table)?
这会发生吗?
最终解决的思路是:
- 每个table的
BEFORE INSERT OR UPDATE
触发器可以推送一次事务:RDB$SET_CONTEXT('USER_TRANSACTION', 'table31', current_timestamp
);
- 如果收到这样的上下文,全局
ON TRANSACTION COMMIT
触发器可以将序列 + 时间插入“日志记录 table”。
- 它甚至可以处理“夏令时变化”和“间隔”,通过仅记录“大时差”,如 >=1 分钟,以减少记录量。)
- 存储过程可以简化并加快每个查询的 '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;
-- other tables too:
T = RDB$GET_CONTEXT('USER_TRANSACTION', 'details_table');
-- ...
when any do EXIT;
end
编辑 1:
在存储过程的帮助下,可以加快从我们的 SYNC_PAST_TIMES
table 读取“上次更改”值的速度。从逻辑上讲,您必须在程序中存储 ID PT_ID
+ 时间 PT_TM
才能为每个 table.
调用它
CREATE PROCEDURE SP_LAST_MODIF_TIME (
TABLE_NUMBER SM_INT,
LAST_PASTTIME_ID BG_INT,
LAST_PASTTIME TIMESTAMP)
RETURNS (
PT_ID BG_INT,
PT_TM TIMESTAMP)
AS
declare variable TEMP_TIME TIMESTAMP;
declare variable TBL SMALLINT;
begin
PT_TM = :LAST_PASTTIME;
FOR SELECT p.ID, p.SYNC_TIME, p.TABLA FROM SYNC_PAST_TIMES p WHERE (p.ID > :LAST_PASTTIME_ID)
ORDER by p.ID ASC
INTO PT_ID, TEMP_TIME, TBL DO --the PT_ID gets an increasing value immediately
begin
if (:TBL = :TABLE_NUMBER) then
if (:TEMP_TIME< :MI_TIME) then
PT_TM = :TEMP_TIME; --searching for the smallest
end
if (:PT_ID IS NULL) then begin
PT_ID = :LAST_PASTTIME_ID;
PT_TM = :LAST_PASTTIME;
end
suspend;
END
您可以通过在 select 中使用此程序,使用 WITH .. AS
格式:
with UTLS as (select first 1 PT_ID, PT_TM from SP_LAST_MODIF_TIME (55, -- TABLE_NUMBER
0, '1899.12.30 00:00:06.000') ) -- last PT_ID, PT_TM from your APP
select first 1000 u.PT_ID, current_timestamp as NOWWW, r.*
from UTLS u, "Orders" r
where (r.SYNC_TIME >= u.PT_TM);
使用 FIRST 1000
是防止读取整个 table 的必要条件,如果所有值都被一次更改的话。
升级 SQL、添加新列等会使 SYNC_TIME
在 table 的所有行同时更改为 NOW
。
您可以根据 table 单独调整它,就像监视变化的秒数间隔一样。给你的APP添加一个check,如果新数据一下子达到1000行,怎么处理...
我的问题有点棘手,因为它主要是一个逻辑问题。
我试图通过将所有内容读入内存来优化我的应用程序速度,但只有那些记录发生了变化,因为 "last read" = 上次加载记录的最大时间戳。
FirebirdSQL 数据库引擎不允许直接更新 "After Trigger" 中的字段,所以它显然使用 "before update or insert" 触发器来更新字段 new.last_changed = current_timestamp;
问题:
事实证明,这是一个完全错误的方法,因为这些触发器在事务 start!
上触发
因此,如果有一个事务比另一个事务花费更多时间,则保存的 "last changed time" 将低于在两者之间触发和完成的短突发事务。
1. tr.: 13:00:01.400 .............................Commit
<< 这条记录将被跳过!
2. tr.: 13:00.01.500......Commit
<< 数据读取将发生在这里。
下一个阅读将是 >= 13:00.01.500
我试过:
重写所有触发器,因此它们会在 之后触发并调用 UPDATE orders SET ...
<< ,但这会导致循环、自调用无限事件。
SET_CONTEXT
lock 会干扰 多行 更新和 嵌套 触发器吗?
(如果 运行 在同一个事务中进行多次更新,我认为此方法没有任何工作的可能性 好 。)
所有这些的通用解决方案是什么?
编辑 1:
我想要发生的是只读取那些来自数据库的记录自上次读取以来实际上发生了变化。为此,我需要引擎在提交后更新记录。 (不是在那期间,"in the middle"。)
这个触发器不好,因为它会在更改时触发,(不是在提交之后):
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;
..此代码将仅列出在第二个事务(较早)中提交的记录,而不会列出第一个、更长的 运行 事务(较晚提交)。
编辑2:
看来我和here...有同样的问题,但是专门针对FirebirdSQL.
那里真的没有什么好的解决方案,但给了我一个想法:
- 如果我创建一个额外的 table 并在每个 table 之前 5 分钟内记录更改怎么办?
- 在每个 SQL 查询之前,首先我会询问 table 中的任何更改,通过 ID grow!
排序
- 删除超过 23 小时的行
ID TableID Changed
===========================
1 5 2019.11.27 19:36:21
2 5 2019.11.27 19:31:19
编辑 3:
正如 Arioch 已经建议的那样,一种解决方案是:
- 创建一个 "logger table" 填充每个
BEFORE INSERT OR UPDATE
由每个 table 触发
- 并更新它的"last_changed"序列
通过
ON TRANSACTION COMMIT
触发器
但是,不会...
更好的方法?:
- 向每个 table 添加 1-1
- 创建全局生成器
LAST_GEN
- 在
ON TRANSACTION COMMIT
触发器 中使用 gen_id(LAST_GEN,1) 更新每个 table 的每个 NULL 行
- 在每个
BEFORE INSERT OR UPDATE
触发时再次设置为 NULL
last_sequence INT64 DEFAULT NULL
列
所以基本上将记录的 last_sequence
列切换为:
NULL > 1 > NULL > 34
...每次修改。
这样我:
- 不必用日志数据填充数据库,
- 我可以直接用
WHERE last_sequence>1;
查询tables。 - 不需要先预查询"logger table"。
我只是害怕:会发生什么,如果 ON TRANSACTION COMMIT
触发器试图更新 last_sequence
字段,而 第二个事务在 ON BEFORE触发器正在锁定记录(另一个 table)?
这会发生吗?
最终解决的思路是:
- 每个table的
BEFORE INSERT OR UPDATE
触发器可以推送一次事务:RDB$SET_CONTEXT('USER_TRANSACTION', 'table31', current_timestamp
); - 如果收到这样的上下文,全局
ON TRANSACTION COMMIT
触发器可以将序列 + 时间插入“日志记录 table”。 - 它甚至可以处理“夏令时变化”和“间隔”,通过仅记录“大时差”,如 >=1 分钟,以减少记录量。)
- 存储过程可以简化并加快每个查询的 '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;
-- other tables too:
T = RDB$GET_CONTEXT('USER_TRANSACTION', 'details_table');
-- ...
when any do EXIT;
end
编辑 1:
在存储过程的帮助下,可以加快从我们的 SYNC_PAST_TIMES
table 读取“上次更改”值的速度。从逻辑上讲,您必须在程序中存储 ID PT_ID
+ 时间 PT_TM
才能为每个 table.
CREATE PROCEDURE SP_LAST_MODIF_TIME (
TABLE_NUMBER SM_INT,
LAST_PASTTIME_ID BG_INT,
LAST_PASTTIME TIMESTAMP)
RETURNS (
PT_ID BG_INT,
PT_TM TIMESTAMP)
AS
declare variable TEMP_TIME TIMESTAMP;
declare variable TBL SMALLINT;
begin
PT_TM = :LAST_PASTTIME;
FOR SELECT p.ID, p.SYNC_TIME, p.TABLA FROM SYNC_PAST_TIMES p WHERE (p.ID > :LAST_PASTTIME_ID)
ORDER by p.ID ASC
INTO PT_ID, TEMP_TIME, TBL DO --the PT_ID gets an increasing value immediately
begin
if (:TBL = :TABLE_NUMBER) then
if (:TEMP_TIME< :MI_TIME) then
PT_TM = :TEMP_TIME; --searching for the smallest
end
if (:PT_ID IS NULL) then begin
PT_ID = :LAST_PASTTIME_ID;
PT_TM = :LAST_PASTTIME;
end
suspend;
END
您可以通过在 select 中使用此程序,使用 WITH .. AS
格式:
with UTLS as (select first 1 PT_ID, PT_TM from SP_LAST_MODIF_TIME (55, -- TABLE_NUMBER
0, '1899.12.30 00:00:06.000') ) -- last PT_ID, PT_TM from your APP
select first 1000 u.PT_ID, current_timestamp as NOWWW, r.*
from UTLS u, "Orders" r
where (r.SYNC_TIME >= u.PT_TM);
使用 FIRST 1000
是防止读取整个 table 的必要条件,如果所有值都被一次更改的话。
升级 SQL、添加新列等会使 SYNC_TIME
在 table 的所有行同时更改为 NOW
。
您可以根据 table 单独调整它,就像监视变化的秒数间隔一样。给你的APP添加一个check,如果新数据一下子达到1000行,怎么处理...