PostgreSQL可以锁定具有特定值的INSERTS吗?

时间:2015-11-17 12:10:16

标签: sql postgresql sql-insert

是否可以在PostgreSQL的INSERT语句中为特定值创建锁?

让我们说,我有这张桌子:

CREATE TABLE IF NOT EXISTS bookings (
bookingID   SERIAL PRIMARY KEY,
tableID     integer REFERENCES tables ON DELETE CASCADE,
startTime   timestamp NOT NULL,
endTime     timestamp NOT NULL,
);

现在,如果启动了带有INSERT的事务块,是否可以在事务时间内为具有相同tableID和相同日期的所有其他INSERTS创建锁定?

因此,同一个tableID的第二次预订必须等待第一次完成。之后可以再次检查,如果INSERT仍然可以。 所以它基本上是INSERTS中特定值的ROW锁。

程序是用Java编写的,但由于瓶颈,我不想使用synchronized块。

感谢您的帮助

3 个答案:

答案 0 :(得分:4)

问题是,为什么要阻止此表的其他插入?

对我而言,您似乎希望确保同一个tableID没有相交的间隔。可能你用Java代码检查这个,你不希望其他插入干扰检查。

如果是这样,您根本不需要锁定:使用EXCLUDE约束。

为此,您需要:

  1. 将两个timestamp字段更改为一个tsrange字段。
  2. 安装btree_gist扩展程序(包含在contrib中)。
  3. 您的表格如下:

    CREATE TABLE IF NOT EXISTS bookings (
      bookingID   SERIAL PRIMARY KEY,
      tableID     integer REFERENCES tables ON DELETE CASCADE,
      during      tsrange NOT NULL,
      EXCLUDE using gist(during with &&, tableID with =)
    );
    

    将自动创建特殊GIST索引,以确保相同的tableID(&&运算符)不存在交叉间隔(=运算符)。

    一些例子:

    -- interval for tableID=10
    test=# insert into bookings values (1, 10, '[2015-11-17 10:00, 2015-11-17 12:00)');
    INSERT 0 1
    
    -- interval for tableID=11
    test=# insert into bookings values (2, 11, '[2015-11-17 10:00, 2015-11-17 12:00)');
    INSERT 0 1
    
    -- can't create intersecting interval for tableID=10
    test=# insert into bookings values (3, 10, '[2015-11-17 11:00, 2015-11-17 13:00)');
    ERROR:  conflicting key value violates exclusion constraint "bookings_during_tableid_excl"
    DETAIL:  Key (during, tableid)=(["2015-11-17 11:00:00","2015-11-17 13:00:00"), 10) conflicts with existing key (during, tableid)=(["2015-11-17 10:00:00","2015-11-17 12:00:00"), 10).
    
    -- ok to create non-intersecting interval
    test=# insert into bookings values (4, 10, '[2015-11-17 12:00, 2015-11-17 13:00)');
    INSERT 0 1
    

答案 1 :(得分:2)

您想要的是谓词锁定。它不是PostgreSQL直接支持的,但@ stas.yaranov所描述的咨询锁定是一个很好的方法。正如@EgorRogov指出的那样,如果可能的话,你应该通过使用适当的约束来完全取消锁定。

没有人提到的另一个选择是在PostgreSQL 9.1或更新版本中使用SERIALIZABLE事务隔离。这使用了一种与乐观锁定非常相似的并发控制方法,其中每个事务都在没有锁定的情况下继续进行,但如果它与其他事务发生冲突,则其中一个可能会中止这意味着您的应用程序必须准备好捕获序列化失败错误并重试事务,但它通常是一种非常有效且非常简单的方法来处理这样的事情 - 特别是在排除约束不会帮助您的情况下

如果你能这样做,我建议在这种特殊情况下使用排除约束,因为这正是它们的设计目的。但是,您可以在不更改架构或添加新索引的情况下使用serializable隔离,这是一种更通用的解决方案。

(顺便说一下,你不能在9.0或更早版本中使用SERIALIZABLE,因为它在这些版本中不够智能)

答案 2 :(得分:1)

同意@a_horse_with_no_name。当然,同步块将是更好的解决方案。

但如果你坚持使用数据库锁,那么advisory lockstoo)可以帮助你。

例如:

begin; -- we will use transaction level lock but there is an option with session level locks
select pg_advisory_xact_lock(123); -- 123 is a tableID you need to lock
insert into bookings(tableID, ...) values(123, ...); -- or/and other queries
commit;