【问题标题】:Using a Repo in an Ecto migration在 Ecto 迁移中使用回购
【发布时间】:2018-01-22 22:20:13
【问题描述】:

我有一个 Ecto 迁移,我想修改一些列,但也迁移一些数据。例如:

import Ecto.Query

defmodule MyApp.Repo.Migrations.AddStatus do
  alter table(:foo) do
    add(:status, :text)
  end

  foos = from(f in MyApp.Foo, where: ...)
         |> MyApp.Repo.all

  Enum.each(foos, fn(foo) ->
    # There's then some complex logic here to work 
    # out how to set the status based on other attributes of `foo`
  end)
end

现在,这里的问题是,通过调用 MyApp.Repo.all,迁移实质上使用了一个单独的数据库连接到 alter table... 语句使用的那个(EDIT:这个假设是错误的,请参阅接受的答案)。因此,没有status 列,所以整个迁移都被炸毁了!请注意,我们使用的是 postgres 数据库,因此 DDL 语句是事务性的。

可以将此作为两个单独的迁移或mix 任务来设置数据,仅将迁移保留为架构更改,但为了确保数据一致性,我不希望这样做。

对于如何以这种方式为MyApp.Repo 查询使用相同的数据库连接有什么想法吗?

编辑:注意,我正在处理一小组数据,在我的用例中停机时间是可以接受的。如果情况并非如此,请参阅下面 José 的回复,了解一些好的建议。

【问题讨论】:

  • alter 之后添加对flush() 的调用是否有效?
  • 是的,flush() 应该可以工作。整个迁移都包含在一个事务中,这意味着任何 DDL 语句以及 Repo 表达式都在同一个连接/事务上工作。问题是我们将 ddl 语句组合在一起,直到事务结束或刷新。 @Dogbert 应该提交一个答案。 :) 我将编辑我的答案以删除错误的建议。
  • 那么flush() 会提交事务吗?如果在迁移的其余部分出现异常怎么办? flush() 之前的更改也会回滚吗?
  • 不,flush() 不提交事务,它只是执行在那之前已经排队的语句。将迁移中的每个语句视为被推入队列,然后在所有内容都排队后全部运行。 flush() 本质上让您在添加后续语句之前执行已经排队的内容。整个迁移都包含在一个事务中,因此如果发生异常,所有已执行的内容(不管flush())都会回滚。
  • 对于添加上下文@ŠtefanĽupták,将事物添加到队列的方式是通过Ecto.Migration DSL 函数,如alter()create()。我需要flush() 的原因是,在上面的示例中,我正在做其他事情,假设上面的所有内容都已运行。所以迁移失败了,因为当我尝试随后在Enum.each() 中使用它时,我将东西放入队列但实际上并没有在数据库上运行它。

标签: elixir database-migration ecto


【解决方案1】:

您可以通过调用Ecto.Migration.flush/0 在迁移中执行当前待定更改。之后的任何代码都将具有可用的状态字段。

defmodule MyApp.Repo.Migrations.AddStatus do
  alter table(:foo) do
    add(:status, :text)
  end

  flush()

  foos = from(f in MyApp.Foo, where: ...)
         |> MyApp.Repo.all

  ...
end

【讨论】:

    【解决方案2】:

    一般来说,同时进行数据迁移和更改 DDL 是一种不好的做法。如果这是一个实时系统并且迁移需要很长时间,您可能会在很长一段时间内产生大量争用。

    如果您的应用程序仍在处理请求,则可以在处理数据的同时添加新条目,而不会处理这些条目!

    有不同的方法来解决这个问题,具体取决于用例,但它们通常需要一步一步的方法。例如,如果您要添加新列:

    • 第一步是在数据库中引入新列,并确保在创建新条目时填充新列。此步骤只是为了保证所有未来的条目都将正确填充。

    • 那么第二步就是迁移旧数据

    • 最后,您可以启用新数据,因为您可以假设所有条目都已正确填充

    【讨论】:

    • 谢谢何塞!我绝对同意这一点: > 如果这是一个实时系统并且迁移需要很长时间,您可能会在很长一段时间内产生大量争用。但是,我可能应该警告这一点,因为我只查看约 100 行的内部应用程序,我们有一个小的非工作时间维护窗口。
    • 如果无法访问连接(或者访问非常困难),我可能会在第二次迁移中执行此操作。我们已经在分阶段推出,但是维护窗口让我们可以灵活地编写稍微少一点的代码,跳过一两步就可以了:)。
    • 我在我的主要问题中添加了一个 EDIT 来指向这篇文章,因为我认为这很重要,但我认为 Dogbert 可能通过调用 flush() 提供了一个替代方案,这似乎有效。你能解释一下为什么这对我有用吗?我对存在不同的连接有误吗,只是 alter 语句还没有实际上运行?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 2019-01-03
    • 1970-01-01
    相关资源
    最近更新 更多