美文网首页Django中高级知识&应用
如何实现Django migration中DML操作的回滚(下)

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

作者: Congroo_027c | 来源:发表于2019-05-10 18:39 被阅读0次

    分析

    接上一节,我们已经知道了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文件里只出现一次这两种操作之一,这个全文件回滚的功能实现还有意义吗?

    相关文章

      网友评论

        本文标题:如何实现Django migration中DML操作的回滚(下)

        本文链接:https://www.haomeiwen.com/subject/zqqzoqtx.html