2017-02-13 35 views
3

我想在一個函數內部創建一個事務塊,所以我的目標是在一次使用這個函數,所以如果有人使用這個函數而另一個想使用它,他不能,直到第一個是完成我創建這個功能:PostgreSQL - 開始一個事務塊IN函數

CREATE OR REPLACE FUNCTION my_job(time_to_wait integer) RETURNS INTEGER AS $$ 
DECLARE 
    max INT; 
BEGIN 
    BEGIN; 
     SELECT MAX(max_value) INTO max FROM sch_lock.table_concurente; 
     INSERT INTO sch_lock.table_concurente(max_value, date_insertion) VALUES(max + 1, now()); 
     -- Sleep a wail 
     PERFORM pg_sleep(time_to_wait); 
     RETURN max; 
    COMMIT; 
END; 
$$ 
LANGUAGE plpgsql; 

但接縫不工作,我有一個錯誤,語法錯誤BEGIN;

沒有BEGIN;COMMIT我得到一個正確的結果,我用這個查詢來檢查:

-- First user should to wait 10 second 
SELECT my_job(10) as max_value; 

-- First user should to wait 3 second 
SELECT my_job(3) as max_value; 

所以結果是:

+-----+----------------------------+------------+ 
| id |    date   | max_value | 
+-----+----------------------------+------------+ 
| 1 | 2017-02-13 13:03:58.12+00 |  1  | 
+-----|----------------------------+------------+ 
| 2 | 2017-02-13 13:10:00.291+00 |  2  | 
+-----+----------------------------+------------+ 
| 3 | 2017-02-13 13:10:00.291+00 |  2  | 
+-----+----------------------------+------------+ 

但結果應該是:

+-----+----------------------------+------------+ 
| id |    date   | max_value | 
+-----+----------------------------+------------+ 
| 1 | 2017-02-13 13:03:58.12+00 |  1  | 
+-----|----------------------------+------------+ 
| 2 | 2017-02-13 13:10:00.291+00 |  2  | 
+-----+----------------------------+------------+ 
| 3 | 2017-02-13 13:10:00.291+00 |  3  | 
+-----+----------------------------+------------+ 

所以第三個id = 3應該有max_value = 3而不是2,出現這種情況是因爲第一個用戶選擇max = 1並等待10 sec和第二個用戶選擇max = 1並在插入前等待3 sec,但正確的解決方案是:我不能使用這個功能,直到第一個完成,因爲我想做一些安全和保護。

我的問題是:

  • 我怎樣才能使一個事務塊的一個函數裏?
  • 您有什麼建議嗎?我們如何以安全的方式做到這一點?

謝謝。

+0

不幸的是,這是不可能的。函數不能使用提交或回滾。 –

+0

噢,我的上帝,我應該怎麼做@a_horse_with_no_name任何建議? –

+0

你爲什麼不簡單地使用一個序列來生成數字。這將是一個**很快**並且更具可擴展性。 –

回答

2

好,所以你不能在COMMIT函數。但是,您可以擁有一個保存點並回滾到保存點。

您的最小可能事務是服務器從客戶端解析並執行的單個語句,因此每個事務都是一個函數。但是,在交易中,您可以擁有保存點。在這種情況下,您會查看PostgreSQL的異常處理部分來處理這個問題。

但是,這不是你想要的。您希望(我認爲?)數據在長時間運行的服務器端操作期間可見。爲此,你有點不幸。運行某個功能時,您無法真正增加您的交易ID。

您有幾種選擇,在我會認爲是好的做法順序(從最好到最差):

  1. 打破你的邏輯成更小的片段,每個移動數據庫從一個一致的狀態另一個,並在單獨的交易中運行。
  2. 在數據庫中使用消息隊列(如pg_message_queue),外加一個外部worker,以及運行一個步驟併產生下一步消息的東西。缺點是這增加了更多的維護。
  3. 使用像dblink或pl/python或pl/perlu這樣的函數或框架連接到db並在那裏運行事務。 ICK ....
+0

這真的很傷心:(好吧我會盡量按照你的建議,謝謝你的任何方式:) –

+0

Chris Travers檢查我的答案我不知道它是不是一個好主意,但它給了我正確的結果 –

1

你可以使用這個dblink。喜歡的東西:

CREATE OR REPLACE FUNCTION my_job(time_to_wait integer) RETURNS INTEGER AS $$ 
DECLARE 
    max INT; 
BEGIN 
    SELECT INTO RES dblink_connect('con','dbname=local'); 
    SELECT INTO RES dblink_exec('con', 'BEGIN'); 
    ... 
    SELECT INTO RES dblink_exec('con', 'COMMIT'); 
    SELECT INTO RES dblink_disconnect('con'); 
END; 
$$ 
LANGUAGE plpgsql; 
1

如果我們使用LOCK TABLE例如像這樣的,我不知道這是一個很好的方式或沒有,但什麼:

CREATE OR REPLACE FUNCTION my_job(time_to_wait integer) RETURNS INTEGER AS $$ 
DECLARE 
    max INT; 
    BEGIN 
     -- Lock table so no one will use it until the first one is finish 
     LOCK TABLE sch_lock.table_concurente IN ACCESS EXCLUSIVE MODE; 

     SELECT MAX(max_value) INTO max FROM sch_lock.table_concurente; 
     INSERT INTO sch_lock.table_concurente(max_value, date_insertion) VALUES(max + 1, now()); 
     PERFORM pg_sleep(time_to_wait); 
     RETURN max; 
    END; 
    $$ 
LANGUAGE plpgsql; 

它給了我正確的結果。

+0

據我瞭解的問題是,用戶2需要查看當前在用戶1的查詢中發生的插入。我不認爲這解決了這個問題。 –

+0

@ChrisTravers這個工作很好,所以當第一次使用完成時它插入'max_value = 2',所以第二個用戶選擇這個值並使用它來插入'max_value = 3',所以我們有一個安全的訪問這個值,它在當時只有一個用戶使用 –

+0

啊所以你的解決方案是一個表鎖。我想這也適用。 –