【问题标题】:How to disable DDL transaction in an alembic migration如何在 Alembic 迁移中禁用 DDL 事务
【发布时间】:2019-05-07 14:09:19
【问题描述】:

我正在尝试运行 alembic 事务。但是,只要支持事务,所有迁移都会在事务中运行(请参阅Run alembic upgrade migrations in a transaction)。如何禁用特定迁移的事务?

【问题讨论】:

    标签: python sqlalchemy migration alembic


    【解决方案1】:

    这可以使用自动提交块来完成:

    with op.get_context().autocommit_block():
      op.execute(...)
    

    https://alembic.sqlalchemy.org/en/latest/api/runtime.html#alembic.runtime.migration.MigrationContext.autocommit_block

    此特殊指令旨在支持偶尔需要在任何类型的事务块之外运行的数据库 DDL 或系统操作。 PostgreSQL 数据库平台是这种操作风格最常见的目标,因为它的许多 DDL 操作必须在事务块之外运行,即使数据库整体支持事务 DDL。

    请注意,有一些警告:

    警告:根据需要,无条件提交块之前的数据库事务。这意味着在整个迁移操作完成之前,将提交操作之前的迁移运行。 建议当应用程序包含带有“自动提交”块的迁移时,使用 EnvironmentContext.transaction_per_migration 以便调整调用环境以预期每个文件的短迁移,无论其中一个是否具有自动提交块。

    【讨论】:

      【解决方案2】:

      Alembic 过去只有两种使用事务的模式:

      • 整个迁移命令的一个事务。如果要应用多个版本,则它们都在同一个事务中运行。
      • 每个迁移步骤使用单独的事务。

      但是,从 version 1.2.0(2019 年 9 月发布)开始,您现在还可以使用 MigrationContext.autocommit_block() context manager 切换到 AUTOCOMMIT transaction level。在这种事务模式下,每个语句都会立即提交。请注意,使用此功能有一些注意事项,请参见下文。

      默认情况下使用单个事务,但您可以在 env.py 脚本中调用 context.configure()transaction_per_migration 设置为 true 以使用单独的事务。

      使用单个事务的第一个默认选项在 Alembic 为您生成的 env.py 文件中执行,在该文件的 run_migrations_online() 函数中:

      try:
          with context.begin_transaction():
              context.run_migrations()
      finally:
          connection.close()
      

      您可以编辑该文件以删除 with context.begin_transaction(): 上下文管理器,或使用 context.get_x_argument() feature 根据命令行开关切换事务:

      try:
          # Python 3.7+
          from contextlib import nullcontext
      except ImportError:
          # Earlier Python versions
          from contextlib import contextmanager
          @contextmanager
          def nullcontext():
              yield
      
      # ...
      
      def run_migrations_online():
          # ...
          if context.get_x_argument(as_dictionary=True).get('no-transaction', False):
              transaction_cm = nullcontext()
          else:
              transaction_cm = context.begin_transaction()
          try:
              with transaction_cm:
                  context.run_migrations()
          finally:
              connection.close()
      

      要在每个迁移步骤或特定操作中禁用事务,您可以使用前面提到的autocommit_block(),它旨在用于数据库需要在事务上下文之外运行的 DDL 语句:

      def upgrade():
          with op.get_context().autocommit_block():
              op.execute("ALTER TYPE mood ADD VALUE 'soso'")
      

      上面的示例(取自文档)使用Operations.get_context() method 来访问迁移上下文。在上下文中,所有语句都直接执行,而不是在事务中运行。

      需要注意的是,当前正在进行的任何事务都将首先提交。如果这样一个块之前和之后的语句是连接的,并且不应该在没有其他语句的情况下执行,那么你要避免在两者之间放置一个autocommit_block()。您可能还想设置transaction_per_migration = true,并在整个迁移步骤中使用autocommit_block()。这样,您至少可以最大限度地减少迁移步骤中途失败的问题。

      在 1.2.0 版之前,每个迁移步骤禁用事务并不容易。您必须完全禁用事务(只是不要在env.py 中使用context.begin_transaction()),然后根据upgrade()downgrade() 步骤明确使用事务:

      def run_migrations_online():
          # ...
      
          try:
              # no with context.begin_transaction() here
              context.run_migrations()
          finally:
              connection.close()
      

      在每个迁移步骤中:

      def upgrade():
          with context.begin_transaction():
              # ### commands auto generated by Alembic - please adjust! ###
              op.create_table(
                  # ...
              )
              # etc.
      

      【讨论】:

      • 这是我担心的唯一解决方案。谢谢!
      • 现在很容易禁用事务。 @yoz 在下面的答案中对此进行了描述。 (我正在使用他的解决方案。)
      • @JeffYounker:感谢您告诉我! yoz 描述的功能是在version 1.2.0 中添加的,于 2019 年 9 月发布,距离我写完答案 7 个月(也就是我上次接触这篇文章后约 2 周),所以我当时并不知道。
      猜你喜欢
      • 2014-04-01
      • 2016-09-06
      • 2020-02-17
      • 2013-07-04
      • 2014-05-18
      • 2020-03-18
      • 2019-03-09
      • 2013-07-06
      • 2016-06-29
      相关资源
      最近更新 更多