Postgresql触发器:检查WHEN条件下的总行数

时间:2014-01-04 02:15:18

标签: postgresql triggers

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

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

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

知道怎么处理这个吗?有没有其他方法来配置触发器?或者我应该在触发器之外检查该阈值?

3 个答案:

答案 0 :(得分:2)

你不能用触发器WHEN条件做到这一点,如果可能的话也不会这样做。如果有两个并发插入,则触发条件将运行WHEN条件两次,两者都会看到表中有99行,而两者都允许插入下一行而没有相应的删除

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

  • 锁定表格IN EXCLUSIVE MODE
  • COUNT
  • DELETE是最老的行(如果适用)

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

唯一真正的方法是在此表中一次只使用一个事务,或者在使用表LOCK TABLE ... IN EXCLUSIVE MODE之前始终拥有使用表NOTIFY的事务。两者都要求客户端应用程序知道发生了什么。

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

  • 当表格中有插入内容时,请发送LISTEN
  • NOTIFY程序在看到EXCLUSIVE MODE时唤醒,抓取一个阻止插入/更新/删除但允许选择的全表NOTIFY锁,然后删除多余的行。

关于这一点的一个方便之处是{{1}}仅在事务提交时发送。所以你不会遇到同样的锁定问题。

答案 1 :(得分:1)

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;

答案 2 :(得分:0)

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

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在回答后的评论中所建议的那样,如果你的兴趣只是为了保持你的桌子很小,那么定期的cron会有更好的表现。

  2. 由于基数较低,你只需要100行就可以保证在你访问它时随时都有过滤后的seq扫描计划,所以在运行explain analyze时不要对此感到困惑。