2017-07-31 82 views
1

我有多個併發任務,他們都試圖檢查記錄存在,然後,如果不存在,將插入一個。處理併發任務

不幸的是,我最終將重複寫入記錄到DB中,因爲似乎所有任務都決定記錄不會同時存在,那麼所有這些任務都會執行插入操作。

所需的行爲是,我只插入一次,然後,其他任務會識別插入記錄的存在。

這裏是我的嘗試:

alias MyApp.Parent, as: Parent 
alias MyApp.Repo, as: Repo 
changeset = Parent.changeset(%Parent{}, model) 

case Repo.all(from p in Parent, where: p.mobile_number == ^model.mobile_number) do 

    [] -> 
    #does not exist 
     case Repo.insert_or_update(changeset) do 
     {:ok, %MyApp.Parent{ id: parent_id }} -> parent_id 
     error_message -> nil 
     end 


    [parent_get_by|t] -> 
    #already exist 
    %MyApp.Parent{ id: parent_id }= parent_get_by 
     parent_id 

end 

任何幫助表示讚賞!

回答

2

您應該在mobile_number字段的數據庫中添加一個UNIQUE INDEX。它會更高效(只需對數據庫執行一次查詢),並保證數據庫永遠不會在表中具有該字段的重複值。

你需要做三件事情:

  1. 添加unique_index表使用遷移。

  2. 在您的變更功能中添加致電unique_constraint

  3. 在你的控制器中,只要做Repo.insert(changeset)。如果該字段重複,則會返回{:error, changeset},並返回changeset.errors中的錯誤消息。

+0

那麼,那麼當我得到一個錯誤,我會閱讀使用'mobile_number'? – simo

+0

如果您想要將現有記錄返回(如果存在),稍後可以執行'Repo.get_by(Parent,mobile_number:model.mobile_number)'。 – Dogbert

+0

如何使用'insert_or_update'?那麼它只會更新它是否存在並返回記錄,是否比使用'get_by'更好地存在現有記錄? – simo

0

接受的答案很好。

但是,當你不能執行一個唯一的索引,你有兩個選擇。首先是使用鎖定,以防止進程同時查詢數據庫。有關此示例,請參見elixir_locker

另一種方法是序列化請求。這意味着,您將有一個流程,最好是GenServer,它將執行SELECT+INSERT。然後讓你的進程發送消息給它,而不是查詢數據庫本身。這使請求一個接一個地運行,只有第一個請求會被插入,其他請求將被讀取。當然,如果你有很多請求,這個過程本身可能會成爲一個瓶頸。