Django中高级知识&应用

如何实现Django migration中DML操作的回滚(下)

2019-05-10  本文已影响0人  Congroo_027c

分析

接上一节,我们已经知道了python manage.py migrate命令实际上是通过执行migration文件中的migrations.Migration类里的apply方法来实现DDL和DML操作的,自然这里就需要对apply方法进行一番分析了

@python_2_unicode_compatible
class Migration(object):
    ...

    def __init__(self, name, app_label):
        self.name = name
        self.app_label = app_label
        # Copy dependencies & other attrs as we might mutate them at runtime
        self.operations = list(self.__class__.operations)
        self.dependencies = list(self.__class__.dependencies)
        self.run_before = list(self.__class__.run_before)
        self.replaces = list(self.__class__.replaces)    

    def apply(self, project_state, schema_editor, collect_sql=False):
        """
        Takes a project_state representing all migrations prior to this one
        and a schema_editor for a live database and applies the migration
        in a forwards order.

        Returns the resulting project state for efficient re-use by following
        Migrations.
        """
        for operation in self.operations:
            # If this operation cannot be represented as SQL, place a comment
            # there instead
            if collect_sql:
                schema_editor.collected_sql.append("--")
                if not operation.reduces_to_sql:
                    schema_editor.collected_sql.append(
                        "-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:"
                    )
                schema_editor.collected_sql.append("-- %s" % operation.describe())
                schema_editor.collected_sql.append("--")
                if not operation.reduces_to_sql:
                    continue
            # Save the state before the operation has run
            old_state = project_state.clone()
            operation.state_forwards(self.app_label, project_state)
            # Run the operation
            if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:
                # We're forcing a transaction on a non-transactional-DDL backend
                with atomic(schema_editor.connection.alias):
                    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
            else:
                # Normal behaviour
                operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
        return project_state

    ...

这里值得注意的是__init__方法里的几个属性的初始化,其实从这些属性的名字就不难看出他们是对migration文件中的几个关键属性的一一对应例如dependencies、operations,同时通过apply方法我们也可以看出migration过程就是一个对self.operations中的元素进行逐一处理的for循环,既然清楚了原理,解决方案也就自然出来了:给apply方法添加事务
PS:从apply代码也可以看出来Django对每个DML操作默认加上了事务

解决问题

虽然说是解决问题,但这里只是提供一种简单的解决方案而已,毕竟本人水平渣渣也想不出其他方法了(:зゝ∠)

class NewMigration(migrations.Migration):
    def apply(self, project_state, schema_editor, collect_sql=False):
        with transaction.atomic():
            project_state = super(NewMigration, self).apply(project_state, schema_editor, collect_sql)
        return project_state

上面就是一个大概的实现方法,即直接使用Django底层的数据库事务功能重写apply方法,若还想继续针对RunPython和RunSQL命令进行一些特殊处理

值得思考的问题

代码看完,问题得到初步解决了,但是这里其实还有一些比较值得仔细思考的问题

  1. apply中本身也有事务存在,双层包裹会引发其他问题吗?
  2. RunPython,RunSQL本身支持事务,如果一个migration文件里只出现一次这两种操作之一,这个全文件回滚的功能实现还有意义吗?
上一篇下一篇

猜你喜欢

热点阅读