2014-09-02 69 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