6

我遇到了一个问题,我有一个函数需要根据某些情况进行序列化访问。这似乎是使用咨询锁的好案例。但是,在相当重的负载下,我发现没有发生序列化访问,并且我看到了对该函数的并发访问。

此功能的目的是为事件提供“库存控制”。意思是,它旨在限制给定活动的同时购买门票,以使该活动不会超卖。这些是应用程序/数据库中唯一使用的咨询锁。

我发现有时事件中的票数超过 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 (django.db.backends.postgresql_psycopg2 w/CONN_MAX_AGE 300)
  • PGBouncer 1.7.2(会话模式)
  • Amazon RDS 上的 Postgres 9.3.10

我尝试调整的其他变量:

  • 将 CONN_MAX_AGE 设置为 0
  • 删除 pgbouncer 并直接连接到 DB

在我的测试中,我注意到,在某个活动超卖的情况下,门票是从不同的网络服务器购买的,所以我认为共享会话没有什么有趣的事情,但我不能肯定地说。

4

1 回答 1

5

一旦PERFORM pg_advisory_unlock(eventId)执行,另一个会话就可以获取该锁,但是由于会话#1 的INSERT 尚未提交,它不会被计入COUNT(*)会话#2,从而导致超额预订。

如果保持咨询锁策略,您必须使用事务级咨询锁 ( pg_advisory_xact_lock),而不是会话级。这些锁在 COMMIT 时自动释放。

于 2016-05-05T19:52:22.423 回答