我遇到一个问题,我有一个功能,根据某些情况需要序列化访问。这似乎是使用咨询锁的好例子。但是,在相当繁重的负载下,我发现序列化访问没有发生,我看到并发访问该函数。
此功能的目的是为事件提供“库存控制”。这意味着,它旨在限制特定事件的并发票购买,以使事件不被超卖。这些是应用程序/数据库中使用的唯一建议锁。
我发现事件中偶尔会有比eventTicketMax值更多的票证。由于咨询锁定,这似乎不可能。在低容量测试时(或在获取锁定后手动引入延迟,例如pg_sleep),事情按预期工作。
CREATE OR REPLACE FUNCTION createTicket(
userId int,
eventId int,
eventTicketMax int
) RETURNS integer AS $$
DECLARE insertedId int;
DECLARE numTickets int;
BEGIN
-- first get the event lock
PERFORM pg_advisory_lock(eventId);
-- make sure we aren't over ticket max
numTickets := (SELECT count(*) FROM api_ticket
WHERE event_id = eventId and status <> 'x');
IF numTickets >= eventTicketMax THEN
-- raise an exception if this puts us over the max
-- and bail
PERFORM pg_advisory_unlock(eventId);
RAISE EXCEPTION 'Maximum entries number for this event has been reached.';
END IF;
-- create the ticket
INSERT INTO api_ticket (
user_id,
event_id,
created_ts
)
VALUES (
userId,
eventId,
now()
)
RETURNING id INTO insertedId;
-- update the ticket count
UPDATE api_event SET ticket_count = numTickets + 1 WHERE id = eventId;
-- release the event lock
PERFORM pg_advisory_unlock(eventId);
RETURN insertedId;
END;
$$ LANGUAGE plpgsql;
这是我的环境设置:
我尝试调整的其他变量:
在我的测试中,我注意到,在事件超卖的情况下,门票是从不同的网络服务器购买的,所以我认为关于共享会话没有任何有趣的业务,但我不能肯定地说
答案 0 :(得分:4)
执行PERFORM pg_advisory_unlock(eventId)
后,另一个会话可以获取该锁定,但由于会话#1的INSERT尚未提交,因此不会计入会话#2的COUNT(*)
导致超额预订。
如果保留咨询锁定策略,则应使用事务级别的咨询锁定(pg_advisory_xact_lock
),而不是会话级别。这些锁在COMMIT时自动释放。
另请注意,事务隔离级别很重要。默认情况下,postgres使用REPEATABLE READ
,看起来很快就看出Django并没有偏离它。让COUNT(*)
反映最后提交的状态非常重要,而不是事务开始时的状态。