Postgresql EXTO insert\u或\u update创建多个插入?

Postgresql EXTO insert\u或\u update创建多个插入?,postgresql,elixir,phoenix-framework,ecto,Postgresql,Elixir,Phoenix Framework,Ecto,我的代码导致数据库中罕见的两次或三次插入,我不知道为什么。这是很难复制,但我可以看看时间戳,看到创建的时间基本上是相同的,当它发生。我相信只有当CardMeta尚未找到时才会发生 我想我需要添加一个唯一的密钥或将其包装到事务中 def get_or_create_meta(user, card) do case Repo.all(from c in CardMeta, where: c.user_id == ^user.id, where: c.card_id == ^c

我的代码导致数据库中罕见的两次或三次插入,我不知道为什么。这是很难复制,但我可以看看时间戳,看到创建的时间基本上是相同的,当它发生。我相信只有当CardMeta尚未找到时才会发生

我想我需要添加一个唯一的密钥或将其包装到事务中

  def get_or_create_meta(user, card) do
    case Repo.all(from c in CardMeta, where: c.user_id == ^user.id,
      where: c.card_id == ^card.id) do
        [] ->
          %CardMeta{}
        metas ->
          hd metas
    end   
  end

  def bury(user, card) do
    get_or_create_meta(user, card)
    |> Repo.preload([:card, :user])
    |> CardMeta.changeset(%{last_seen: DateTime.utc_now(), user_id: user.id, card_id: card.id,
      learning: false, known: false, prev_interval: 0})
    |> Repo.insert_or_update
  end
编辑:添加变更集源

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
                      :user_id, :card_id])
    |> assoc_constraint(:user)
    |> assoc_constraint(:card)
  end
从控制器调用bury

def update(conn, %{"currentCardId" => card_id, "command" => command}) do
    # perform some update on card
    card = Repo.get!(Card,card_id)
    user = Guardian.Plug.current_resource(conn)

    case command do
      "fail" ->
        SpacedRepetition.fail(user, card)
      "learn" ->
        SpacedRepetition.learn(user, card)
      _ ->
        SpacedRepetition.bury(user, card)
    end
    sendNextCard(conn, user)
  end
编辑:


我注意到,在重复的行之间,最后一次看到的字段相差微秒,而create_at字段没有这样的分辨率。因此,我怀疑insert_或_update调用没有问题,但控制器在DB更新之前触发了两次。这可能是客户端的问题,我不想去想。所以我只想添加一个唯一的密钥。

我相信您可以通过在用户id和卡id上添加一个复合主键来解决这个问题


如果这不能解决您的问题,请在此处添加您的数据模型

如果您不想更改CardMeta上的主键,可以通过迁移在数据库中设置唯一索引约束,以替代@aliCna的回答:

defmodule YourApp.Repo.Migrations.AddCardMetaUniqueIndex do
  use Ecto.Migration

  def change do
    create unique_index(
      :card_meta, 
      [:card_id, :user_id], 
      name: :card_meta_unique_index)
  end
end
然后,如果发生冲突,您可以在变更集中处理这些问题,以产生良好的错误:

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
                    :user_id, :card_id])
  |> assoc_constraint(:user)
  |> assoc_constraint(:card)
  |> unique_constraint(:user_id, name: :card_meta_unique_index)
end

你能发布CardMeta.changeset的源代码和调用bury的代码吗?试试乐观锁定,看看这是否有帮助。控制器的更新操作可能存在并发问题。您可以尝试的一件事是将bury代码放在GenServer中,并触发一个调用。通过这种方式,您将确保没有其他请求进入。我发现另一件奇怪的事情是在卸载的架构上运行Repo.preload。我必须测试它才能相信它有效。
def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
                    :user_id, :card_id])
  |> assoc_constraint(:user)
  |> assoc_constraint(:card)
  |> unique_constraint(:user_id, name: :card_meta_unique_index)
end