【问题标题】:Postgresql trigger: check total number of rows in WHEN conditonPostgresql触发器:检查WHEN条件下的总行数
【发布时间】:2014-01-07 08:38:56
【问题描述】:

我有一个表,它需要一个触发器来保持总行数为 100。触发器的工作方式是,一旦有新行插入表中,最旧的行将被删除。显然,我需要在触发器开始工作之前检查总行数是否已经达到 100。

我考虑使用WHEN (condition)作为触发器,这里的条件可以是子查询(SELECT count(*) FROM mytablename)>100。但问题是目前 WHEN 条件不支持子查询。

不幸的是,我没有找到另一种方法来计算表中的行数而不编写查询。

知道如何处理吗?有没有其他方法来配置触发器?还是我应该检查触发器之外的阈值?

【问题讨论】:

  • 我很想看看你为什么要这样做。将表限制为 100 行的目标是什么?你必须有多严格?你关心你是否得到 104 行?
  • @CraigRinger 好点!我不在乎我是否得到 104 行!严格来说可以不超过 100 行,但应该比这更大,并且不会超过很多。如果你问我为什么要这样做,我不得不说它只是需要这样。

标签: postgresql triggers


【解决方案1】:

您不能使用触发器WHEN 条件来执行此操作,如果可以这样做也没有任何意义。如果有两个并发插入,您的触发条件将运行 WHEN 条件两次,两者都会看到表中有 99 行,并且都将允许插入下一行而无需相应的删除强>.

由于 PostgreSQL 不支持 SQL 断言(有什么作用?),您最好的选择是始终在每次插入时运行的触发器。这个触发器:

  • 锁定表IN EXCLUSIVE MODE
  • COUNTs 行
  • DELETEs 最旧的行(如果合适)

...然而,有一个皱纹。 LOCK,虽然是必要的,但就是所谓的“锁定升级”。该事务将始终在表上拥有一个锁,但它会是一个较弱的锁。这是在并发环境中在并发事务之间创建死锁的一种近乎保证的方式,因为两个或多个事务具有较弱的锁,并且它们每个都希望被彼此在表上的弱锁阻塞的更强的锁。

解决这个问题的唯一真正方法是一次只对这个表使用一个事务,或者在对表做任何事情之前总是让事务使用表LOCK TABLE ... IN EXCLUSIVE MODE。两者都要求客户端应用程序知道发生了什么。

总的来说,我认为在 SQL 表中严格固定行数并不是一个好主意。你还没有解释为什么你想要这个,所以我很难就替代方案提出建议,但一种可能性可能是让客户端应用程序容忍超出行数限制的小幅增加,并且做懒惰的清理:

  • 当有插入到表中时,发送NOTIFY
  • 当看到NOTIFY 时唤醒LISTENing 程序,获取阻止插入/更新/删除但允许选择的整表EXCLUSIVE MODE 锁,然后删除多余的行。

这方面的方便之处在于,NOTIFY 仅在事务提交时发送。所以你不会遇到同样类型的锁定问题。

【讨论】:

  • 非常感谢您的解释!我不需要严格的 100 行,稍微多一点就可以了。实际上,我只是在寻找一种在保持足够记录(例如 100 条最新记录)的同时最小化表大小的方法。这有意义吗?
  • @HaoHuang 是的,这是有道理的,为此我强烈建议只执行定期作业(cron 作业等)并清除旧记录。使用触发器进行操作可能会很痛苦。
【解决方案2】:

正如Craig's answer 解释的那样,无法在触发器中可靠地测试其他行上的此类条件。

但是,我相信一个简单的方法是可能的,借助一个序列和表中的一个附加列。

初始化:

  • 创建一个从 1 开始到 100 循环的序列。
  • 为行号添加intsmallintRN

插入逻辑(实际上是一种合并):

  • 询问序列的 nextval (SN)
  • 更新与RN=SN 匹配的行(每列获取新行的值)(如果存在)。将其视为“回收”行。
  • 如果更新不影响任何行,则将其作为新行插入,RNSN 为值。

RN 应始终是唯一的,并且介于 1 和 100 之间。 当多个事务同时插入时,它们将针对不同的RN,因此它们不会相互锁定。但是,如果超过 100 个事务同时执行此操作,则可能会发生锁定。

例子:

CREATE SEQUENCE cycle_seq maxvalue 100 cycle;
CREATE TABLE tst(val1 int, val2 int, RN int);

CREATE FUNCTION cycling_insert(_val1 int, _val2 int) returns void AS
$$
declare
  _rn int:=nextval('cycle_seq');
begin
   UPDATE tst SET val1=_val1,val2=_val2 WHERE RN=_rn;
   IF NOT FOUND THEN
     INSERT INTO tst VALUES(_val1,_val2,_rn);
   END IF;
END $$ language plpgsql;

【讨论】:

  • 好主意,我也想过使用这种方法,但还没有一个完整的解决方案。从上面与其他人的讨论中,我发现我的设计要求要宽松得多,所以我选择了其他答案中描述的周期性 cron 方法。我可能会在我的设计中的其他地方使用您的方法,非常感谢!
【解决方案3】:

如果我的问题和要求正确,则不需要 when 条件。如果您有适当的排序标准来选择那些太旧的行,您可以直接删除多余的行,例如:

create function trim_tbl() returns trigger as $$
begin
  delete from tbl
  where id in (select id from tbl order by id desc offset 100);
  return null;
end;
$$ language plpgsql;

create trigger trim_tbl after insert on tbl
for each row execute procedure trim_tbl();

也可能是语句级触发器。

话虽如此:

  1. 正如@CraigRinger 在他回答后在 cmets 中所建议的那样,如果您的兴趣只是保持表大小较小,那么使用周期性 cron 将获得更好的性能。

  2. 由于基数低,您只有 100 行,基本上保证了过滤后的 seq 扫描计划,因为基数低,所以在运行解释分析时不要对此感到困惑。

【讨论】:

  • Where 条件而不是When 条件,很有趣。我已经选择了定期 cron,但你的回答很有帮助,也许我可以在我的设计中的其他地方采用它。非常感谢!
猜你喜欢
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
相关资源
最近更新 更多