【问题标题】:Postgres insert row only if row count is under a limit仅当行数低于限制时 Postgres 才插入行
【发布时间】:2018-02-11 10:53:35
【问题描述】:

我想在 Postgres 10 中有一个 SQL 语句或函数,仅当行数少于一定数量时才允许我插入一行。

select count(*) from mytable where mykey = 1
--- do the following only if the above is less than 5
insert into mytable (myvalue, mykey) values ('randomvalue', 1)
--- so there are never more than 5 rows in mytable with mykey = 1

这样的伪代码是否可以在应用程序中工作(多次往返调用 postgres 服务器)?

tx = app.tx("start transaction");
count = tx.query("select count(*) from mytable where mykey = 1")
if (count < 5) {
  tx.query("insert into mytable (myvalue, mykey) values ('randomvalue', 1)")
}
tx.do("commit")

如果它不起作用,我怎么能在 Postgres 端这样做,所以我只从应用程序拨打一个电话,比如select myinsertfunction('randomvalue', 1)

无论是上面的多次往返方式,还是对 postgres 的一次调用,最重要的是,这不可能以插入超过 5 行的方式并行运行。例如,事务 1 和事务 2 都检查计数是否为 4(小于最大值 5),并同时进行,因此它们最终都会插入 1 行,导致总共 6 行而不是最多 5 个。

【问题讨论】:

标签: sql postgresql


【解决方案1】:

这个问题被称为Phantom Read

一个事务重新执行一个查询,返回一组满足搜索条件的行,并发现满足条件的行集由于另一个最近提交的事务而发生了变化。

试试

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO mytable (myvalue, mykey) SELECT 'randomvalue', 1 WHERE
    (SELECT COUNT(*) FROM mytable WHERE mykey = 1) < 5;
END;

事务隔离级别将确保事务仅在计数小于 5 时才插入值。

【讨论】:

  • 不会也将事务级别设置为可重复读取工作吗?你能描述一个它不起作用但可序列化的场景吗?
  • 不,这通常不起作用,因为REPEATABLE READ(或更低)不会保护整个表不被修改。而且因为您使用COUNT 访问整个表,所以您必须防止其他进程更改它。但这无关紧要:“当您选择未提交的级别时,您确实获得了已提交的阅读,而当您选择可重复读取时,您确实获得了可序列化”(请参阅​​上面的链接文档)。
  • 在这种情况下使用可序列化的成本是多少(与默认的读取提交相反)。例如,从mytable 读取或写入的所有其他查询是否会阻塞,直到此事务提交或回滚?
  • 不,不一定。事务序列化很难在评论中解释,但您可以阅读不同的来源,例如:WikipediaPostgres
  • 虽然这个答案有效,但有人可以向我解释为什么 OP 的伪代码不起作用吗?换句话说,即使我们使用可序列化事务,为什么在应用层检查计数还是不行?我之所以问,是因为我遇到了这个确切的问题,我尝试在应用层检查计数,但没有成功。
猜你喜欢
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-30
  • 2011-03-25
相关资源
最近更新 更多