PostgreSQL 锁定具有特定值的 INSERTS 可能吗?
PostgreSQL lock INSERTS with specific values possible?
是否可以在 PostgreSQL 的 INSERT 语句中为特定值创建锁?
比方说,我有这个 table:
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 编写的,但由于瓶颈,我不想使用同步块。
感谢您的帮助
同意@a_horse_with_no_name。当然,同步块将是更好的解决方案。
但如果您坚持使用数据库锁,那么advisory locks (too) 可以帮助您。
例如:
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;
问题是,为什么要为此 table 阻止其他插入?
对我来说,您似乎想要确保同一 tableID 没有相交的间隔。可能您在 Java 代码中检查了这个并且您不希望其他插入干扰检查。
如果是这样,你根本不需要锁:使用EXCLUDE约束。
为此,您需要:
- 将两个
timestamp
字段更改为一个 tsrange
字段。
- 安装 btree_gist 扩展(它包含在贡献中)。
您的 table 将如下所示:
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
你想要的叫做谓词锁定。 PostgreSQL 不直接支持它,但是 @stas.yaranov 描述的咨询锁定是一个很好的解决方法。正如@EgorRogov 指出的那样,如果可能,您应该通过使用适当的约束完全消除锁定的需要。
另一个没有人提到的选项是在 PostgreSQL 9.1 或更新版本中使用 SERIALIZABLE
事务隔离。这使用了一种非常类似于乐观锁定的并发控制方法,其中每个事务都在不锁定的情况下进行,但是如果其中一个与另一个发生冲突,则可能会中止。这意味着您的应用程序必须准备好捕获序列化失败错误并重试事务,但它通常是处理此类事情的一种非常有效且非常简单的方法 - 特别是在排除约束对您没有帮助的情况下。
如果可以的话,我建议在此特定情况下使用排除约束,因为这正是它们的设计目的。但是您可以使用 serializable
隔离而无需更改架构或添加新索引,这是一个更通用的解决方案。
(顺便说一句,在 9.0 或更早版本中你不能为此使用 SERIALIZABLE
,因为它在那些版本中不够智能)
是否可以在 PostgreSQL 的 INSERT 语句中为特定值创建锁?
比方说,我有这个 table:
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 编写的,但由于瓶颈,我不想使用同步块。
感谢您的帮助
同意@a_horse_with_no_name。当然,同步块将是更好的解决方案。
但如果您坚持使用数据库锁,那么advisory locks (too) 可以帮助您。
例如:
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;
问题是,为什么要为此 table 阻止其他插入?
对我来说,您似乎想要确保同一 tableID 没有相交的间隔。可能您在 Java 代码中检查了这个并且您不希望其他插入干扰检查。
如果是这样,你根本不需要锁:使用EXCLUDE约束。
为此,您需要:
- 将两个
timestamp
字段更改为一个tsrange
字段。 - 安装 btree_gist 扩展(它包含在贡献中)。
您的 table 将如下所示:
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
你想要的叫做谓词锁定。 PostgreSQL 不直接支持它,但是 @stas.yaranov 描述的咨询锁定是一个很好的解决方法。正如@EgorRogov 指出的那样,如果可能,您应该通过使用适当的约束完全消除锁定的需要。
另一个没有人提到的选项是在 PostgreSQL 9.1 或更新版本中使用 SERIALIZABLE
事务隔离。这使用了一种非常类似于乐观锁定的并发控制方法,其中每个事务都在不锁定的情况下进行,但是如果其中一个与另一个发生冲突,则可能会中止。这意味着您的应用程序必须准备好捕获序列化失败错误并重试事务,但它通常是处理此类事情的一种非常有效且非常简单的方法 - 特别是在排除约束对您没有帮助的情况下。
如果可以的话,我建议在此特定情况下使用排除约束,因为这正是它们的设计目的。但是您可以使用 serializable
隔离而无需更改架构或添加新索引,这是一个更通用的解决方案。
(顺便说一句,在 9.0 或更早版本中你不能为此使用 SERIALIZABLE
,因为它在那些版本中不够智能)