2014-09-02 62 views
2

之间我已经具有以下字段的表:中强制Postgres的宽松的限制多个客户端

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 | ...  | ... 
+0

怎么样在应用了'UNIQUE'指数和处理插入错误? – 2014-09-02 11:44:00

+0

如何在一段时间内做一个UNIQUE语句? (site_id,uuid,user_ip,user_agent)可以一起设置为唯一,但只要没有两个session_start/session_end时间戳在彼此的3小时内,就允许多行。 – 2014-09-02 11:47:43

+0

听起来像那些时间戳是自然的关键。 – supertopi 2014-09-02 11:52:08

回答

2

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 && 
    ) 
); 

创建gist排除约束现在,插入失败:

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

+0

很好的建议,但为了方便实施和更容易更改约束条件(比如3到5个小时),我将使用咨询锁解决方案。我不知道排除主要限制,所以谢谢你教我:) – 2014-09-02 13:33:36

+0

@Christian:我不明白。第一个进程锁定和插入。然后另一个进程等待,直到释放锁并插入一个侵权元组。这不是你想要避免的吗?你还等什么? – 2014-09-02 13:41:25

+0

我做了一些其他检查,所以我将在整个管道启动之前基本保持锁定,然后在最后释放它,防止任何元组被插入错误。 推论,更快 - 尝试和有一个约束错误或检查,然后插入? – 2014-09-03 10:28:16

1

退房咨询locks

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

或者使用打头阻断版本:

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

啊,这将允许我只锁定一个小一次请求的子集,这将是完美的! – 2014-09-02 13:16:10

+0

@ChristianP。是的,你在这里很灵活,你可以定义你自己的密钥,可以覆盖你想要的许多组。 – Vlad 2014-09-02 13:33:44

+0

准确。我可能只是添加不同的字段,不包括时间戳和锁定(计算a。模式是,前2-4个请求相继进入,然后他们间隔出来,使其更少的问题。干杯! – 2014-09-02 13:37:50