2017-07-31 86 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。然后让你的进程发送消息给它,而不是查询数据库本身。这使请求一个接一个地运行,只有第一个请求会被插入,其他请求将被读取。当然,如果你有很多请求,这个过程本身可能会成为一个瓶颈。