如何实现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命令进行一些特殊处理
值得思考的问题
代码看完,问题得到初步解决了,但是这里其实还有一些比较值得仔细思考的问题
- apply中本身也有事务存在,双层包裹会引发其他问题吗?
- RunPython,RunSQL本身支持事务,如果一个migration文件里只出现一次这两种操作之一,这个全文件回滚的功能实现还有意义吗?