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



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

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

不幸的是,在不编写查询的情况下,我未能找到另一种方法来计算表中的行数。

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

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

由于PostgreSQL不支持SQL断言(做了什么?),您最好的选择是在每次插入时都运行触发器。此触发器:

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

。。。然而,有一条皱纹。LOCK虽然是必要的,但它被称为"锁升级"。事务在表上总是有一个锁,但它将是一个较弱的锁。在并发环境中,这是一种几乎可以保证在并发事务之间创建死锁的方法,因为两个或多个事务具有较弱的锁,并且它们都想要一个更强的锁,而该锁被表上其他事务的弱锁阻塞。

解决这一问题的唯一真正方法是,一次只使用一个事务处理此表,或者在使用表LOCK TABLE ... IN EXCLUSIVE MODE之前始终使用该表的事务。两者都要求客户端应用程序了解发生了什么。

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

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

这方面的便利之处在于,只有在事务提交时才会发送NOTIFY。这样就不会出现同样的锁定问题。

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

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

初始化:

  • 创建一个从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;

如果我理解您的问题和要求,就不需要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在回答后的评论中所建议的那样,如果你的兴趣只是保持表的小尺寸,那么使用周期性cron会获得更好的性能。

  2. 由于基数较低,您只有100行,这从根本上保证了您在任何时候访问它时都能得到一个经过过滤的seq扫描计划,所以在运行explain analysis时不要对此感到困惑。

最新更新