我遇到了一个问题,我有一个函数,根据某些情况需要序列化访问。这似乎是使用咨询锁的好案例。然而,在相当大的负载下,我发现序列化访问并没有发生,我看到了对该函数的并发访问。
该函数的目的是为事件提供“库存控制”。也就是说,它旨在限制给定事件的并发购票,以使事件不被超售。这些是应用程序/数据库中仅使用的咨询锁。
我发现有时会有更多的门票比eventTicketMax值多。由于使用了咨询锁,这似乎不可能发生。当进行低容量测试(或手动引入延迟,如在获取锁后pg_sleep)时,一切都按预期工作。
这是我的环境设置:
该函数的目的是为事件提供“库存控制”。也就是说,它旨在限制给定事件的并发购票,以使事件不被超售。这些是应用程序/数据库中仅使用的咨询锁。
我发现有时会有更多的门票比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;
这是我的环境设置:
- Django 1.8.1 (使用CONN_MAX_AGE 300的django.db.backends.postgresql_psycopg2)
- PGBouncer 1.7.2 (会话模式)
- Amazon RDS上的Postgres 9.3.10
- 将CONN_MAX_AGE设置为0
- 移除pgbouncer并直接连接到数据库