在多个客户端之间执行postgres中的宽松约束

时间:2014-09-02 11:29:51

标签: sql postgresql constraints race-condition

我有一个包含以下字段的表:

entry_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
uuid VARCHAR(256) NOT NULL,
session_start TIMESTAMP NOT NULL,
session_end TIMESTAMP NOT NULL,
user_ip VARCHAR(40) NOT NULL,
user_agent VARCHAR(256) NOT NULL,

现在,我有很多传入请求,其中包含(site_id, uuid, timestamp, user_ip, user_agent)行的数据元组。

我的规则是,如果数据库中的条目少于3个小时(session_end),则传入请求会更新session_end = timestamp。如果没有,请创建一个新条目(session_start = session_end = timestamp)。

传入的请求由多个进程处理。所以说3-4个传入的请求使用相同的数据(不同的时间戳,但是毫秒级)命中我的服务器并由3个不同的进程处理 - 我如何避免创建3个不同的记录(如果它们同时检查,请参阅没有记录匹配,每个都创建一个新的)?这是一个竞争条件问题,我不确定如何执行它。

表锁似乎有些过分,因为这是一个写得很大的表,但除了第三方锁定机制之外还有什么替代方法?

示例:

Format:
(site_id, uuid, timestamp, user_ip, user_agent)

Incoming requests / data:
(1, 123, 2014-01-01T10:00:32, '123.123.123.123', 'Mozilla/Chrome')
(1, 123, 2014-01-01T10:00:33, '123.123.123.123', 'Mozilla/Chrome')
(1, 123, 2014-01-01T10:00:34, '123.123.123.123', 'Mozilla/Chrome')

Result tuple:
entry_id | site_id | uuid | session_start       | session_end         | user_ip | user_agent
--------------------------------------------------------------------------------------------
<auto>   |       1 |  123 | 2014-01-01T10:00:32 | 2014-01-01T10:00:34 | ...     | ...

2 个答案:

答案 0 :(得分:2)

使用gist类型

创建timestamp range排除约束
create table request (
    entry_id bigserial primary key,
    site_id bigint not null,
    uuid varchar(256) not null,
    session_start timestamp not null,
    session_end timestamp not null,
    user_ip varchar(40) not null,
    user_agent varchar(256) not null,
    constraint session_overlap exclude using gist (
        site_id with =,
        uuid with =,
        user_ip with =,
        user_agent with =,
        tsrange(session_end, session_end + interval '3 hours', '[)') with &&
    )
);

现在插入失败了:

insert into request (site_id, uuid, session_start, session_end, user_ip, user_agent)
select site_id, uuid, ts::timestamp, ts::timestamp, user_id, user_agent
from (values
    (1, '123', '2014-01-01T10:00:32', '123.123.123.123', 'Mozilla/Chrome'),
    (1, '123', '2014-01-01T10:00:33', '123.123.123.123', 'Mozilla/Chrome'),
    (1, '123', '2014-01-01T10:00:34', '123.123.123.123', 'Mozilla/Chrome')
) s(site_id, uuid, ts, user_id, user_agent)
;
ERROR:  conflicting key value violates exclusion constraint "session_overlap"
DETAIL:  Key (site_id, uuid, user_ip, user_agent, tsrange(session_end, session_end + '03:00:00'::interval, '[)'::text))=(1, 123, 123.123.123.123, Mozilla/Chrome, ["2014-01-01 10:00:33","2014-01-01 13:00:33")) conflicts with existing key (site_id, uuid, user_ip, user_agent, tsrange(session_end, session_end + '03:00:00'::interval, '[)'::text))=(1, 123, 123.123.123.123, Mozilla/Chrome, ["2014-01-01 10:00:32","2014-01-01 13:00:32")).

您可能需要以超级用户

的身份安装btree_gist扩展程序
create extension btree_gist;

http://www.postgresql.org/docs/current/interactive/btree-gist.html

答案 1 :(得分:1)

查看公告locks

SELECT pg_advisory_lock(key);
// INSERT OR UPDATE...
SELECT pg_advisory_unlock(key);

或使用nob-blocking版本:

SELECT pg_try_advisory_lock(key) INTO :acquired;
// if (acquired) then INSERT OR UPDATE...
SELECT pg_advisory_unlock(key);