返回介绍

14.2 数据库的迁移

发布于 2024-01-21 17:11:03 字数 10466 浏览 0 评论 0 收藏 0

14.2.1 什么是数据库的迁移

数据库的迁移(Migration)是对数据库中的模式定义以及数据转移进行管理的功能。它的主要作用是版本管理以及版本升级、回滚等,类似于 Ruby 的 Ruby on Rails 框架。

那么,在什么场合下才能体会到它的便捷之处呢?我们经过亲身实践,认为它在以下场合能发挥极大作用。

◉ 多人持续开发时

多人共同开发的时候,应用程序经常要反映别人的修改,所以模式升级和回滚的机会也相对较多。

◉ 需要在运行过程中修改数据表时

如果需要在运行过程中修改数据表,就必须修改模式以防止现有数据受损。另外,一旦修改后出现问题,还需要立刻进行恢复。这种时候就必须做好版本管理,保证随时可以回滚。

14.2.2 Django 的迁移功能

◉ Django 数据库迁移

Django 的迁移功能支持给模型添加新的字段、向数据库的列添加 null=Ture 等。在版本管理方面,Django 以模型为基准,通过生成并执行迁移文件来完成数据库版本的更新,从而实现版本管理。

迁移文件同时也是从零新建数据库的方法之一。第一个迁移文件的执行结果是从空模式迁移为初始状态的数据表。等到所有迁移文件执行完毕之后,我们就会得到应用了最新版本的数据库模式。另外,如果有上一个版本的数据库,那么只需执行最后生成的迁移文件即可得到最新的数据库模式。

◉ 示例(对新建的应用套用迁移时)

接下来我们以新建应用的情况为例来学习一下迁移功能。首先新建一个应用(LIST 14.1)。

LIST 14.1 新建 polls 应用

$ python manage.py startapp polls

新建 polls 应用之后,首先在 settings.py 的 INSTALLED_APPS 中添加 polls,然后在 polls/models.py 中创建模型(LIST 14.2)。

LIST 14.2 myprj/settings.py

INSTALLED_APPS = (
  ...
  'polls',
)

polls 应用需要投票项目(poll)和选项(choice)两个模型,这里我们只创建 poll。poll 中要包含题目(question_test)的信息(LIST 14.3)。

LIST 14.3 polls/models.py

# -*- coding: utf-8 -*-
from django.db import models
class Poll(models.Model):
  question_text = models.CharField(max_length=200)

接下来要做的是生成迁移文件。迁移文件通过 makemigrations 命令生成(LIST 14.4)。

LIST 14.4 生成第一个迁移文件

$ python manage.py makemigrations polls
Migrations for 'polls':
  0001_initial.py:
  - Create model Poll

执行上述命令后,会生成 polls/migrations/0001_initial.py 迁移文件。此时数据库中还没有 polls,所以这里我们需要通过 migrate 命令执行迁移文件,从而生成 polls(LIST 14.5)。

LIST 14.5 执行 migrate

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK

此时会发现 poll 还缺少公布日期(Publication Date),于是我们还需要为它添加公布日期(LIST 14.6)。

LIST 14.6 polls/models.py

# -*- coding: utf-8 -*-
from django.db import models
class Poll(models.Model):
  question_text = models.CharField(max_length=200)
  # 添加公布日期
  pub_date = models.DateTimeField('date published')

此类细微修改也能通过迁移功能完成。做修改时也要用 makemigrations 命令生成迁移文件(LIST 14.7)。

LIST 14.7 生成用于修改的迁移文件

$ python manage.py makemigrations polls
You are trying to add a non-nullable field 'pub_date' to poll without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py

此时,计算机表示“pub_date 中没有设置 default”,询问如何处理已有数据。选择 1 可以向已有数据输入要设置的值,选择 2 则可以中断迁移文件的生成。这里我们选择 1。计算机告诉我们可以使用 datetime module 和 Django 的 timezone mudule,这里我们选择 timezone.now() (有时区概念的当前时间。向数据库中添加的值为 UTC)(LIST 14.8)。

LIST 14.8 设置 default 数据

Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> timezone.now()
Migrations for 'polls':
  0002_poll_pub_date.py:
  - Add field pub_date to poll

到此,用于修改的迁移文件生成完毕。只生成迁移文件并不能将修改反映到数据库中,因此别忘了执行 migrate (LIST 14.9)。

LIST 14.9 执行 migrate

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
  Applying polls.0002_poll_pub_date... OK

这样一来,修改就反映到数据库中了。

以上就是迁移的基本使用方法。只要记住 makemigrations 和 migrate 这两个命令,就能够应付绝大部分情况。不过例外总还是有的,为此我们来简单了解一下各个命令。

◉ 命令简介

migrate

用于执行迁移文件的命令(LIST 14.10、LIST 14.11)。

LIST 14.10 指定应用执行

$ python manage.py migrate application

LIST 14.11 指定文件名或编号执行

$ python manage.py migrate application file_name_or_number

该命令可以指定应用或文件名来执行。如果不指定,则会执行所有应用尚未执行过的迁移文件。

· --fake

该选项可以将未执行的迁移文件标记为已执行,但不实际执行该文件。比如存在编号很旧的未执行的迁移文件,但当前的数据库状态下执行该文件会出现错误时,就可以使用这个选项。

makemigrations

用于生成迁移文件的命令。

· --empty

用于生成空迁移文件的选项。在我们想手动编写迁移文件的内容,或者需要生成用于数据迁移的文件时,可以使用该选项。关于数据迁移的详细内容,我们将在“数据迁移”部分学习。

sqlmigrate

该命令可以查看套用迁移文件时执行的 SQL(LIST 14.12)。

LIST 14.12 查看执行的 SQL

$ python manage.py sqlmigrate application file_name_or_number

◉ 数据迁移

学习 makemigrations 的 --empty 选项时我们了解到,Django 可以借助迁移功能做数据迁移。数据迁移是伴随着值的变化的数据库模式变更,为与一般的模式迁移作区分,才被称为数据迁移。下面我们试着做一个让 poll 的公布日期延后一天的数据迁移(LIST 14.13、LIST 14.14)。

LIST 14.13 生成用于数据迁移的文件

$ python manage.py makemigrations --empty polls
Migrations for 'polls':
0003_auto_20141104_0236.py:

LIST 14.14 0003_auto_20141104_0236.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
  dependencies = [
    ('polls', '0002_poll_pub_date'),
  ]
  operations = [
  ]

生成的迁移文件中只有类的定义和空的方法,并没有描述实际的处理。我们只需添加数据迁移所需的处理,即可用它来做数据迁移(LIST 14.15)。

LIST 14.15 添加让公布日期延后一天的数据迁移

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
from django.db import models, migrations
def forward_pub_date_by_one_day(apps, schema_editor):
  Poll = apps.get_model('polls', 'Poll')
  for poll in Poll.objects.all():
    poll.pub_date += datetime.timedelta(days=1)
    poll.save()
def backward_pub_date_by_one_day(apps, schema_editor):
  Poll = apps.get_model('polls', 'Poll')
  for poll in Poll.objects.all():
    poll.pub_date -= datetime.timedelta(days=1)
    poll.save()
class Migration(migrations.Migration):
  dependencies = [
    ('polls', '0002_poll_pub_date'),
  ]
  operations = [
    migrations.RunPython(forward_pub_date_by_one_day,
               backward_pub_date_by_one_day)
  ]

添加完处理之后,执行migrate ,将其反映到数据库中(LIST 14.16)。

LIST 14.16 执行 migrate

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
  Applying polls.0003_auto_20141104_0236... OK

想了解数据迁移的更多内容可以参考 Django 的迁移文档 1

1 https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations

◉ 迁移文件的 squash

随着迁移的频繁使用,迁移文件会越积越多,执行时间自然越来越长。如果想缩短时间,就需要将多个迁移文件合并成一个,这时可以使用 squashmigrations 命令(LIST 14.17)。比如在发布 Web 应用时将所有迁移文件合并成一个,这样一来我们在搭建环境时就只需要执行一个迁移。

LIST 14.17 迁移文件的 squash

$ python manage.py squashmigrations polls 0003
Will squash the following migrations:
 - 0001_initial
 - 0002_poll_pub_date
 - 0003_auto_20141104_0236
Do you wish to proceed? [yN]

计算机询问是否执行,这里键入 y 并执行(LIST 14.18)。

LIST 14.18 迁移文件的 squash

Do you wish to proceed? [yN] y
Optimizing...
  Optimized from 3 operations to 2 operations.
Created new squashed migration /path/to/myprj/polls/migrations/0001_squashed_0003_auto_20141104_0236.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.
Manual porting required
  Your migrations contained functions that must be manually copied over,
  as we could not safely copy their implementation.
  See the comment at the top of the squashed migration for details

执行后生成了名为 0001_squashed_0003_auto_20141104_0236.py 的新文件。这个迁移文件相当于之前生成的 0001、0002、0003 的总和。往后在新建的数据库中执行 migrate 时会使用这个新的迁移文件,而在已有数据库中则使用原来的迁移文件。

不过,从执行 squashmigrations 时输出的信息来看,我们需要手动修改新文件。这是因为之前做数据迁移时,0003_auto_201141104_0236 是手动添加的。新建数据库进行迁移时不需要进行数据迁移,所以我们可以将数据迁移的相关处理从 0001_squashed_0003_auto_20141104_0236 中删除。打开文件,删除 operations 内的 migrations.RunPython(…)。另外,用于添加 pub_date 的迁移文件中设置的当前时间也要一并删除(LIST 14.19)。

LIST 14.19 0001_squashed_0003_auto_20141104_0236.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.utils.timezone import utc
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# polls.migrations.0003_auto_20141104_0236
class Migration(migrations.Migration):
  replaces = [(b'polls', '0001_initial'), (b'polls', '0002_poll_pub_date'), (b'polls', '0003_auto_20141104_0236')]
  dependencies = [
  ]
  operations = [
    migrations.CreateModel(
      name='Poll',
      fields=[
      ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
      ('question_text', models.CharField(max_length=200)),
      # 删除下面第一行,添加下面第二行
      # ('pub_date', models.DateTimeField(default=datetime.datetime (2014, 11, 4, 1, 22, 22, 729029, tzinfo=utc), verbose_name=b'date published')),
      ('pub_date', models.DateTimeField(verbose_name=b'date published')),
    ],
    options={
    },
    bases=(models.Model,),
  ),
  # 删除以下被注释的部分
  # migrations.RunPython(
  #   code=polls.migrations.0003_auto_20141104_0236.forward_pub_date_by_one_day,
  #   reverse_code=polls.migrations.0003_auto_20141104_0236.backward_pub_date_by_one_day,
  #   atomic=True,
  # ),
]

新建数据库进行迁移时,只需要执行 polls 的一个迁移文件。

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying polls.0001_squashed_0003_auto_20141104_0236... OK
  Applying sessions.0001_initial... OK

无论是多人持续开发时还是需要在运行过程中修改数据表时,迁移都能大幅减少劳动力的付出以及出现操作失误的风险。各位请务必试一试,亲自体验其效果。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文