django不停机该程序
Managing database migrations is a great challenge in any software project. Luckily, as of version 1.7, Django comes with a built-in migration framework. The framework is very powerful and useful in managing change in databases. But the flexibility provided by the framework required some compromises. To understand the limitations of Django migrations, you are going to tackle a well known problem: creating an index in Django with no downtime.
在任何软件项目中,管理数据库迁移都是一个巨大的挑战。 幸运的是,从1.7版开始,Django附带了一个内置的迁移框架。 该框架在管理数据库更改方面非常强大且有用。 但是框架提供的灵活性需要一些折衷。 要了解Django迁移的局限性,您将解决一个众所周知的问题:在Django中创建索引而无需停机。
In this tutorial, you’ll learn:
在本教程中,您将学习:
This intermediate-level tutorial is designed for readers who are already familiar with Django migrations. For an introduction to that topic, check out Django Migrations: A Primer.
本中级教程是为已经熟悉Django迁移的读者设计的。 有关该主题的介绍,请查看Django Migrations:A Primer 。
Free Bonus: Click here to get free access to additional Django tutorials and resources you can use to deepen your Python web development skills.
免费红利: 单击此处可免费访问其他Django教程和资源,您可以使用它们来加深Python Web开发技能。
A common change that usually becomes necessary when the data stored by your application grows is adding an index. Indexes are used to speed up queries and make your app feel fast and responsive.
当应用程序存储的数据增长时,通常需要进行的一项常见更改是添加索引。 索引用于加快查询速度,并使您的应用程序快速响应。
In most databases, adding an index requires an exclusive lock on the table. An exclusive lock prevents data modification (DML) operations such as UPDATE
, INSERT
, and DELETE
, while the index is created.
在大多数数据库中,添加索引需要在表上具有排他锁。 排他锁可防止在创建索引时进行数据修改(DML)操作,如UPDATE
, INSERT
和DELETE
。
Locks are obtained implicitly by the database when executing certain operations. For example, when a user logs into your app, Django will update the last_login
field in the auth_user
table. To perform the update, the database will first have to obtain a lock on the row. If the row is currently being locked by another connection, then you might get a database exception.
执行某些操作时,数据库将隐式获得锁。 例如,当用户登录到您的应用程序,Django会更新last_login
领域auth_user
表。 要执行更新,数据库将首先必须获得该行的锁。 如果该行当前被另一个连接锁定,则可能会收到数据库异常 。
Locking a table might pose a problem when it’s necessary to keep the system available during migrations. The bigger the table, the longer it can take to create the index. The longer it takes to create the index, the longer the system is unavailable or unresponsive to users.
当需要在迁移期间保持系统可用时,锁定表可能会带来问题。 表越大,创建索引所花费的时间越长。 创建索引花费的时间越长,系统不可用或对用户无响应的时间就越长。
Some database vendors provide a way to create an index without locking the table. For example, to create an index in PostgreSQL without locking a table, you can use the CONCURRENTLY
keyword:
一些数据库供应商提供了一种在不锁定表的情况下创建索引的方法。 例如,要在PostgreSQL中创建索引而不锁定表,可以使用CONCURRENTLY
关键字:
CREATE CREATE INDEX INDEX CONCURRENTLY CONCURRENTLY ix ix ON ON table table (( columncolumn );
);
In Oracle, there is an ONLINE
option to allow DML operations on the table while the index is created:
在Oracle中,有一个ONLINE
选项允许在创建索引时对表进行DML操作:
When generating migrations, Django will not use these special keywords. Running the migration as is will make the database acquire an exclusive lock on the table and prevent DML operations while the index is created.
生成迁移时,Django将不会使用这些特殊关键字。 按原样运行迁移将使数据库在表上获得排他锁,并在创建索引时阻止DML操作。
Creating an index concurrently has some caveats. It’s important to understand the issues specific to your database backend in advance. For example, one caveat in PostgreSQL is that creating an index concurrently takes longer because it requires an additional table scan.
同时创建索引有一些警告。 事先了解特定于数据库后端的问题很重要。 例如, PostgreSQL中的一个警告是并发创建索引需要更长的时间,因为它需要进行额外的表扫描。
In this tutorial, you’ll use Django migrations to create an index on a large table, without causing any downtime.
在本教程中,您将使用Django迁移在大型表上创建索引,而不会造成任何停机。
Note: To follow this tutorial, it is recommended that you use a PostgreSQL backend, Django 2.x, and Python 3.
注意:要遵循本教程,建议您使用PostgreSQL后端,Django 2.x和Python 3。
It is possible to follow along with other database backends as well. In places where SQL features unique to PostgreSQL are used, change the SQL to match your database backend.
也可以与其他数据库后端一起使用。 在使用PostgreSQL独有SQL功能的地方,更改SQL以匹配您的数据库后端。
You’re going to use a made up Sale
model in an app called app
. In a real life situation, models such as Sale
are the main tables in the database, and they will usually be very big and store a lot of data:
您将在名为app
的应用app
使用组合Sale
模型。 在现实生活中,诸如Sale
类的模型是数据库中的主表,它们通常会很大并且会存储大量数据:
# models.py
# models.py
from from django.db django.db import import models
models
class class SaleSale (( modelsmodels .. ModelModel ):
):
sold_at sold_at = = modelsmodels .. DateTimeFieldDateTimeField (
(
auto_now_addauto_now_add == TrueTrue ,
,
)
)
charged_amount charged_amount = = modelsmodels .. PositiveIntegerFieldPositiveIntegerField ()
()
To create the table, generate the initial migration and apply it:
要创建表,请生成初始迁移并应用它:
After a while, the sales table becomes very big, and users start to complain about slowness. While monitoring the database, you noticed that a lot of queries use the sold_at
column. To speed things up, you decide that you need an index on the column.
一段时间后,销售表变得很大,用户开始抱怨速度缓慢。 在监视数据库时,您注意到很多查询都使用sold_at
列。 为了加快速度,您决定在该列上需要一个索引。
To add an index on sold_at
, you make the following change to the model:
要在sold_at
上添加索引,请对模型进行以下更改:
# models.py
# models.py
from from django.db django.db import import models
models
class class SaleSale (( modelsmodels .. ModelModel ):
):
sold_at sold_at = = modelsmodels .. DateTimeFieldDateTimeField (
(
auto_now_addauto_now_add == TrueTrue ,
,
db_indexdb_index == TrueTrue ,
,
)
)
charged_amount charged_amount = = modelsmodels .. PositiveIntegerFieldPositiveIntegerField ()
()
If you run this migration as it is, then Django will create the index on the table, and it will be locked until the index is completed. It can take a while to create an index on a very large table, and you want to avoid downtime.
如果按原样运行此迁移,则Django将在表上创建索引,并且它将被锁定,直到索引完成为止。 在非常大的表上创建索引可能需要一段时间,并且您希望避免停机。
On a local development environment with a small dataset and very few connections, this migration might feel instantaneous. However, on large datasets with many concurrent connections, obtaining a lock and creating the index can take a while.
在具有少量数据集和很少连接的本地开发环境中,这种迁移可能是瞬时的。 但是,在具有许多并发连接的大型数据集上,获取锁和创建索引可能需要一段时间。
In the next steps, you are going to modify migrations created by Django to create the index without causing any downtime.
在接下来的步骤中,您将修改Django创建的迁移以创建索引而不会造成任何停机。
The first approach is to create the index manually. You are going to generate the migration, but you are not going to actually let Django apply it. Instead, you will run the SQL manually in the database and then make Django think the migration completed.
第一种方法是手动创建索引。 您将要生成迁移,但实际上不会让Django应用它。 相反,您将在数据库中手动运行SQL,然后让Django认为迁移已完成。
First, generate the migration:
首先,生成迁移:
Use the sqlmigrate
command to view the SQL Django will use to execute this migration:
使用sqlmigrate
命令查看Django将用于执行此迁移SQL:
$ python manage.py sqlmigrate app $ python manage.py sqlmigrate app 0002
0002
BEGIN;
BEGIN;
--
--
-- Alter field sold_at on sale
-- Alter field sold_at on sale
--
--
CREATE INDEX "app_sale_sold_at_b9438ae4" ON "app_sale" ("sold_at");
CREATE INDEX "app_sale_sold_at_b9438ae4" ON "app_sale" ("sold_at");
COMMIT;
COMMIT;
You want to create the index without locking the table, so you need to modify the command. Add the CONCURRENTLY
keyword and execute in the database:
您要在不锁定表的情况下创建索引,因此需要修改命令。 添加CONCURRENTLY
关键字并在数据库中执行:
Notice that you executed the command without the BEGIN
and COMMIT
parts. Omitting these keywords will execute the commands without a database transaction. We will discuss database transactions later in the article.
请注意,您执行的命令没有BEGIN
和COMMIT
部分。 省略这些关键字将执行命令而无需数据库事务。 我们将在本文后面讨论数据库事务。
After you executed the command, if you try to apply migrations, then you will get the following error:
执行命令后,如果尝试应用迁移,则会收到以下错误:
$ python manage.py migrate
$ python manage.py migrate
Operations to perform:
Operations to perform:
Apply all migrations: app
Apply all migrations: app
Running migrations:
Running migrations:
Applying app.0002_add_index_fake...Traceback (most recent call last):
Applying app.0002_add_index_fake...Traceback (most recent call last):
File "venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 85, in _execute
File "venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
return self.cursor.execute(sql, params)
psycopg2.ProgrammingError: relation "app_sale_sold_at_b9438ae4" already exists
psycopg2.ProgrammingError: relation "app_sale_sold_at_b9438ae4" already exists
Django complains that the index already exists, so it can’t proceed with the migration. You just created the index directly in the database, so now you need to make Django think that the migration was already applied.
Django抱怨索引已经存在,因此无法继续进行迁移。 您只是直接在数据库中创建了索引,所以现在您需要让Django认为迁移已被应用。
How to Fake a Migration
如何进行迁移
Django provides a built-in way of marking migrations as executed, without actually executing them. To use this option, set the --fake
flag when applying the migration:
Django提供了一种将迁移标记为已执行的内置方式,而无需实际执行。 要使用此选项,请在应用迁移时设置--fake
标志:
Django didn’t raise an error this time. In fact, Django didn’t really apply any migration. It just marked it as executed (or FAKED
).
Django这次没有引发错误。 实际上,Django并没有真正应用任何迁移。 它只是将其标记为已执行(或FAKED
)。
Here are some issues to consider when faking migrations:
伪造迁移时,需要考虑以下一些问题:
The manual command must be equivalent to the SQL generated by Django: You need to make sure the command you execute is equivalent to the SQL generated by Django. Use sqlmigrate
to produce the SQL command. If the commands do not match, then you might end up with inconsistencies between the database and the models state.
Other unapplied migrations will also be faked: When you have multiple unapplied migrations, they will all be faked. Before you apply migrations, it’s important to make sure only the migrations you want to fake are unapplied. Otherwise, you might end up with inconsistencies. Another option is to specify the exact migration you want to fake.
Direct access to the database is required: You need to run the SQL command in the database. This is not always an option. Also, executing commands directly in a production database is dangerous and should be avoided when possible.
Automated deployment processes might need adjustments: If you automated the deployment process (using CI, CD, or other automation tools), then you might need to alter the process to fake migrations. This is not always desirable.
手动命令必须等效于Django生成SQL:您需要确保执行的命令等效于Django生成SQL。 使用sqlmigrate
产生SQL命令。 如果命令不匹配,则可能会导致数据库和模型状态之间不一致。
其他未应用的迁移也将被伪造:当您有多个未应用的迁移时,它们都将被伪造。 在应用迁移之前,确保仅应用要伪造的迁移非常重要。 否则,您可能会出现不一致的情况。 另一个选择是指定您要伪造的确切迁移。
需要直接访问数据库:您需要在数据库中运行SQL命令。 这并非总是一种选择。 同样,直接在生产数据库中执行命令也是很危险的,应尽可能避免执行。
自动化的部署过程可能需要调整:如果您使部署过程自动化 (使用CI,CD或其他自动化工具),则可能需要将过程更改为伪造迁移。 这并不总是可取的。
Cleanup
清理
Before moving on to the next section, you need to bring the database back to its state right after the initial migration. To do that, migrate back to the initial migration:
在继续进行下一部分之前,您需要在初始迁移之后立即将数据库恢复到其状态。 为此,请迁移回初始迁移:
$ python manage.py migrate $ python manage.py migrate 0001
0001
Operations to perform:
Operations to perform:
Target specific migration: 0001_initial, from app
Target specific migration: 0001_initial, from app
Running migrations:
Running migrations:
Rendering model states... DONE
Rendering model states... DONE
Unapplying app.0002_add_index_fake... OK
Unapplying app.0002_add_index_fake... OK
Django unapplied the changes made in the second migration, so now it’s safe to also delete the file:
Django未应用第二次迁移中所做的更改,因此现在也可以删除该文件了:
To make sure you did everything right, inspect the migrations:
为确保您做对了所有事情,请检查迁移情况:
$ python manage.py showmigrations app
$ python manage.py showmigrations app
app
app
[X] 0001_initial
[X] 0001_initial
The initial migration was applied, and there are no unapplied migrations.
已应用初始迁移,并且没有未应用的迁移。
In the previous section, you executed SQL directly in the database and faked the migration. This gets the job done, but there is a better solution.
在上一节中,您直接在数据库中执行SQL并伪造了迁移。 这样就可以完成工作,但是有更好的解决方案。
Django provides a way to execute raw SQL in migrations using RunSQL
. Let’s try to use it instead of executing the command directly in the database.
Django提供了一种使用RunSQL
在迁移中执行原始SQL的方法。 让我们尝试使用它,而不是直接在数据库中执行命令。
First, generate a new empty migration:
首先,生成一个新的空迁移:
Next, edit the migration file and add a RunSQL
operation:
接下来,编辑迁移文件并添加RunSQL
操作:
# migrations/0002_add_index_runsql.py
# migrations/0002_add_index_runsql.py
from from django.db django.db import import migrationsmigrations , , models
models
class class MigrationMigration (( migrationsmigrations .. MigrationMigration ):
):
atomic atomic = = False
False
dependencies dependencies = = [
[
(( 'app''app' , , '0001_initial''0001_initial' ),
),
]
]
operations operations = = [
[
migrationsmigrations .. RunSQLRunSQL (
(
'CREATE INDEX "app_sale_sold_at_b9438ae4" '
'CREATE INDEX "app_sale_sold_at_b9438ae4" '
'ON "app_sale" ("sold_at");''ON "app_sale" ("sold_at");' ,
,
),
),
]
]
When you run the migration, you will get the following output:
运行迁移时,将获得以下输出:
This is looking good, but there is a problem. Let’s try to generate migrations again:
看起来不错,但是有问题。 让我们尝试再次生成迁移:
$ python manage.py makemigrations --name leftover_migration
$ python manage.py makemigrations --name leftover_migration
Migrations for 'app':
Migrations for 'app':
app/migrations/0003_leftover_migration.py
app/migrations/0003_leftover_migration.py
- Alter field sold_at on sale
- Alter field sold_at on sale
Django generated the same migration again. Why did it do that?
Django再次生成了相同的迁移。 为什么这样做呢?
Cleanup
清理
Before we can answer that question, you need to clean up and undo the changes you made to the database. Start by deleting the last migration. It was not applied, so it’s safe to delete:
在我们可以回答这个问题之前,您需要清理并撤消对数据库所做的更改。 首先删除上一次迁移。 它未应用,因此可以安全删除:
Next, list the migrations for the app
app:
接下来,列出app
程序app
程序的迁移:
$ python manage.py showmigrations app
$ python manage.py showmigrations app
app
app
[X] 0001_initial
[X] 0001_initial
[X] 0002_add_index_runsql
[X] 0002_add_index_runsql
The third migration is gone, but the second is applied. You want to get back to the state right after the initial migration. Try to migrate back to the initial migration as you did in the previous section:
第三次迁移已消失,但第二次迁移已应用。 您想在初始迁移后立即回到状态。 尝试像上一节中一样迁移回初始迁移:
Django is unable to reverse the migration.
Django无法撤消迁移。
To reverse a migration, Django executes an opposite action for every operation. In this case, the reverse of adding an index is to drop it. As you’ve already seen, when a migration is reversible, you can unapply it. Just like you can use checkout
in Git, you can reverse a migration if you execute migrate
to an earlier migration.
要撤消迁移,Django会对每个操作执行相反的操作。 在这种情况下,添加索引的相反操作是删除它。 如您所见,当迁移是可逆的时,您可以取消应用它。 就像可以在Git中使用checkout
一样,如果您执行migrate
到较早的迁移,则可以撤消迁移。
Many built-in migration operations already define a reverse action. For example, the reverse action for adding a field is to drop the corresponding column. The reverse action for creating a model is to drop the corresponding table.
许多内置的迁移操作已经定义了反向操作。 例如,添加字段的反向操作是删除相应的列。 创建模型的相反操作是删除相应的表。
Some migration operations are not reversible. For example, there is no reverse action for removing a field or deleting a model, because once the migration was applied, the data is gone.
某些迁移操作是不可逆的。 例如,删除字段或删除模型没有反向操作,因为一旦应用了迁移,数据就消失了。
In the previous section, you used the RunSQL
operation. When you tried to reverse the migration, you encountered an error. According to the error, one of the operations in the migration cannot be reversed. Django is unable to reverse raw SQL by default. Because Django has no knowledge of what was executed by the operation, it cannot generate an opposite action automatically.
在上一节中,您使用了RunSQL
操作。 当您尝试撤消迁移时,遇到错误。 根据错误,迁移中的操作之一无法撤消。 Django默认情况下无法撤消原始SQL。 由于Django不了解该操作执行了什么,因此它无法自动生成相反的动作。
How to Make a Migration Reversible
如何使迁移可逆
For a migration to be reversible, all the operations in it must be reversible. It’s not possible to reverse part of a migration, so a single non-reversible operation will make the entire migration non-reversible.
为了使迁移是可逆的,迁移中的所有操作必须是可逆的。 不可能撤消部分迁移,因此单个不可撤消操作将使整个迁移不可撤消。
To make a RunSQL
operation reversible, you must provide SQL to execute when the operation is reversed. The reverse SQL is provided in the reverse_sql
argument.
要使RunSQL
操作可逆,必须提供SQL以在操作逆向时执行。 reverse_sql
参数中提供了反向SQL。
The opposite action to adding an index is to drop it. To make your migration reversible, provide the reverse_sql
to drop the index:
与添加索引相反的操作是删除索引。 为了使迁移可逆,请提供reverse_sql
删除索引:
# migrations/0002_add_index_runsql.py
# migrations/0002_add_index_runsql.py
from from django.db django.db import import migrationsmigrations , , models
models
class class MigrationMigration (( migrationsmigrations .. MigrationMigration ):
):
atomic atomic = = False
False
dependencies dependencies = = [
[
(( 'app''app' , , '0001_initial''0001_initial' ),
),
]
]
operations operations = = [
[
migrationsmigrations .. RunSQLRunSQL (
(
'CREATE INDEX "app_sale_sold_at_b9438ae4" '
'CREATE INDEX "app_sale_sold_at_b9438ae4" '
'ON "app_sale" ("sold_at");''ON "app_sale" ("sold_at");' ,
,
reverse_sqlreverse_sql == 'DROP INDEX "app_sale_sold_at_b9438ae4";''DROP INDEX "app_sale_sold_at_b9438ae4";' ,
,
),
),
]
]
Now try to reverse the migration:
现在尝试撤消迁移:
The second migration was reversed, and the index was dropped by Django. Now it’s safe to delete the migration file:
第二次迁移被撤消,并且Django删除了索引。 现在可以安全删除迁移文件:
$ rm app/migrations/0002_add_index_runsql.py
$ rm app/migrations/0002_add_index_runsql.py
It’s always a good idea to provide reverse_sql
. In situations where reversing a raw SQL operation does not require any action, you can mark the operation as reversible using the special sentinel migrations.RunSQL.noop
:
提供reverse_sql
总是一个好主意。 在逆向原始SQL操作不需要任何操作的情况下,可以使用特殊的哨兵migrations.RunSQL.noop
将操作标记为可逆.RunSQL.noop:
In your previous attempt to create the index manually using RunSQL
, Django generated the same migration over and over again even though the index was created in the database. To understand why Django did that, you first need to understand how Django decides when to generate new migrations.
在您先前使用RunSQL
手动创建索引的尝试中,即使索引是在数据库中创建的,Django也会一次又一次地生成相同的迁移。 要了解Django为什么这么做,您首先需要了解Django如何决定何时生成新迁移。
In the process of generating and applying migrations, Django syncs between the state of the database and the state of the models. For example, when you add a field to a model, Django adds a column to the table. When you remove a field from the model, Django removes the column from the table.
在生成和应用迁移的过程中,Django在数据库状态与模型状态之间进行同步。 例如,当您将字段添加到模型时,Django会在表中添加一列。 当您从模型中删除字段时,Django将从表中删除该列。
To sync between the models and the database, Django maintains a state that represents the models. To sync the database with the models, Django generates migration operations. Migration operations translate to a vendor specific SQL that can be executed in the database. When all migration operations are executed, the database and the models are expected to be consistent.
为了在模型和数据库之间进行同步,Django维护了一个代表模型的状态。 为了使数据库与模型同步,Django会生成迁移操作。 迁移操作转换为可以在数据库中执行的供应商特定SQL。 执行所有迁移操作后,数据库和模型应保持一致。
To get the state of the database, Django aggregates the operations from all past migrations. When the aggregated state of the migrations is not consistent with the state of the models, Django generates a new migration.
为了获取数据库的状态,Django汇总了过去所有迁移的操作。 当迁移的汇总状态与模型状态不一致时,Django会生成一个新的迁移。
In the previous example, you created the index using raw SQL. Django did not know you created the index because you didn’t use a familiar migration operation.
在上一个示例中,您使用原始SQL创建了索引。 Django不知道您创建了索引,因为您没有使用熟悉的迁移操作。
When Django aggregated all the migrations and compared them with the state of the models, it found that an index was missing. This is why, even after you created the index manually, Django still thought it was missing and generated a new migration for it.
当Django汇总所有迁移并将其与模型状态进行比较时,它发现缺少索引。 这就是为什么即使手动创建了索引后,Django仍然认为该索引已丢失并为其生成了新的迁移。
Since Django is unable to create the index the way you want it to, you want to provide your own SQL but still let Django know you created it.
由于Django无法按照您希望的方式创建索引,因此您想提供自己SQL,但仍要让Django知道您已创建索引。
In other words, you need to execute something in the database and provide Django with the migration operation to sync its internal state. To do that, Django provides us with a special migration operation called SeparateDatabaseAndState
. This operation is not well known and should be reserved for special cases such as this one.
换句话说,您需要在数据库中执行某些操作,并为Django提供迁移操作以同步其内部状态。 为此,Django为我们提供了一个名为SeparateDatabaseAndState
的特殊迁移操作。 此操作尚不为人所知,应保留用于此类特殊情况。
It’s much easier to edit migrations than write them from scratch, so start by generating a migration the usual way:
与从头开始编写迁移相比,编辑迁移要容易得多,因此首先以通常的方式生成迁移:
$ python manage.py makemigrations --name add_index_separate_database_and_state
$ python manage.py makemigrations --name add_index_separate_database_and_state
Migrations for 'app':
Migrations for 'app':
app/migrations/0002_add_index_separate_database_and_state.py
app/migrations/0002_add_index_separate_database_and_state.py
- Alter field sold_at on sale
- Alter field sold_at on sale
This is the contents of the migration generated by Django, same as before:
这是Django产生的迁移内容,与之前相同:
Django generated an AlterField
operation on the field sold_at
. The operation will create an index and update the state. We want to keep this operation but provide a different command to execute in the database.
Django的产生AlterField
场上操作sold_at
。 该操作将创建索引并更新状态。 我们希望保留此操作,但提供不同的命令以在数据库中执行。
Once again, to get the command, use the SQL generated by Django:
再次,要获取命令,请使用Django生成SQL:
$ python manage.py sqlmigrate app $ python manage.py sqlmigrate app 0002
0002
BEGIN;
BEGIN;
--
--
-- Alter field sold_at on sale
-- Alter field sold_at on sale
--
--
CREATE INDEX "app_sale_sold_at_b9438ae4" ON "app_sale" ("sold_at");
CREATE INDEX "app_sale_sold_at_b9438ae4" ON "app_sale" ("sold_at");
COMMIT;
COMMIT;
Add the CONCURRENTLY
keyword in the appropriate place:
在适当的位置添加CONCURRENTLY
关键字:
Next, edit the migration file and use SeparateDatabaseAndState
to provide your modified SQL command for execution:
接下来,编辑迁移文件,并使用SeparateDatabaseAndState
提供修改后SQL命令以执行:
# migrations/0002_add_index_separate_database_and_state.py
# migrations/0002_add_index_separate_database_and_state.py
from from django.db django.db import import migrationsmigrations , , models
models
class class MigrationMigration (( migrationsmigrations .. MigrationMigration ):
):
dependencies dependencies = = [
[
(( 'app''app' , , '0001_initial''0001_initial' ),
),
]
]
operations operations = = [
[
migrationsmigrations .. SeparateDatabaseAndStateSeparateDatabaseAndState (
(
state_operationsstate_operations == [
[
migrationsmigrations .. AlterFieldAlterField (
(
model_namemodel_name == 'sale''sale' ,
,
namename == 'sold_at''sold_at' ,
,
fieldfield == modelsmodels .. DateTimeFieldDateTimeField (
(
auto_now_addauto_now_add == TrueTrue ,
,
db_indexdb_index == TrueTrue ,
,
),
),
),
),
],
],
database_operationsdatabase_operations == [
[
migrationsmigrations .. RunSQLRunSQL (( sqlsql == """
"""
CREATE INDEX CONCURRENTLY "app_sale_sold_at_b9438ae4"
CREATE INDEX CONCURRENTLY "app_sale_sold_at_b9438ae4"
ON "app_sale" ("sold_at");
ON "app_sale" ("sold_at");
""" """ , , reverse_sqlreverse_sql == """
"""
DROP INDEX "app_sale_sold_at_b9438ae4";
DROP INDEX "app_sale_sold_at_b9438ae4";
""" """ ),
),
],
],
),
),
],
],
The migration operation SeparateDatabaseAndState
accepts 2 lists of operations:
迁移操作SeparateDatabaseAndState
接受2个操作列表:
You kept the original operation generated by Django in state_operations
. When using SeparateDatabaseAndState
, this is what you will usually want to do. Notice that the db_index=True
argument is provided to the field. This migration operation will let Django know that there is an index on the field.
您将Django生成的原始操作保留在state_operations
。 使用SeparateDatabaseAndState
,这通常是您想要做的。 请注意,该字段提供了db_index=True
参数。 此迁移操作将使Django知道该字段上有一个索引。
You used the SQL generated by Django and added the CONCURRENTLY
keyword. You used the special action RunSQL
to execute raw SQL in the migration.
您使用了Django生成SQL,并添加了CONCURRENTLY
关键字。 您使用特殊操作RunSQL
在迁移中执行原始SQL。
If you try to run the migration, you will get the following output:
如果尝试运行迁移,将获得以下输出:
In SQL, CREATE
, DROP
, ALTER
, and TRUNCATE
operations are referred to as Data Definition Language (DDL). In databases that support transactional DDL, such as PostgreSQL, Django executes migrations inside a database transaction by default. However, according to the error above, PostgreSQL cannot create an index concurrently inside a transaction block.
在SQL中, CREATE
, DROP
, ALTER
和TRUNCATE
操作称为数据定义语言 (DDL)。 在支持事务性DDL的数据库中( 例如PostgreSQL) ,Django默认在数据库事务内部执行迁移。 但是,根据上述错误,PostgreSQL无法在事务块内并发创建索引。
To be able to create an index concurrently within a migration, you need to tell Django to not execute the migration in a database transaction. To do that, you mark the migration as non-atomic by setting atomic
to False
:
为了能够在迁移中同时创建索引,您需要告诉Django不要在数据库事务中执行迁移。 为此,您可以通过将atomic
设置为False
来将迁移标记为非原子迁移:
# migrations/0002_add_index_separate_database_and_state.py
# migrations/0002_add_index_separate_database_and_state.py
from from django.db django.db import import migrationsmigrations , , models
models
class class MigrationMigration (( migrationsmigrations .. MigrationMigration ):
):
atomic atomic = = False
False
dependencies dependencies = = [
[
(( 'app''app' , , '0001_initial''0001_initial' ),
),
]
]
operations operations = = [
[
migrationsmigrations .. SeparateDatabaseAndStateSeparateDatabaseAndState (
(
state_operationsstate_operations == [
[
migrationsmigrations .. AlterFieldAlterField (
(
model_namemodel_name == 'sale''sale' ,
,
namename == 'sold_at''sold_at' ,
,
fieldfield == modelsmodels .. DateTimeFieldDateTimeField (
(
auto_now_addauto_now_add == TrueTrue ,
,
db_indexdb_index == TrueTrue ,
,
),
),
),
),
],
],
database_operationsdatabase_operations == [
[
migrationsmigrations .. RunSQLRunSQL (( sqlsql == """
"""
CREATE INDEX CONCURRENTLY "app_sale_sold_at_b9438ae4"
CREATE INDEX CONCURRENTLY "app_sale_sold_at_b9438ae4"
ON "app_sale" ("sold_at");
ON "app_sale" ("sold_at");
""" """ ,
,
reverse_sqlreverse_sql == """
"""
DROP INDEX "app_sale_sold_at_b9438ae4";
DROP INDEX "app_sale_sold_at_b9438ae4";
""" """ ),
),
],
],
),
),
],
],
After you marked the migration as non-atomic, you can run the migration:
将迁移标记为非原子迁移之后,可以运行迁移:
You just executed the migration without causing any downtime.
您只是执行了迁移,而没有造成任何停机。
Here are some issues to consider when you’re using SeparateDatabaseAndState
:
在使用SeparateDatabaseAndState
时,需要考虑以下一些问题:
Database operations must be equivalent to state operations: Inconsistencies between the database and model state can cause a lot of trouble. A good starting point is to keep the operations generated by Django in state_operations
and edit the output of sqlmigrate
to use in database_operations
.
Non atomic migrations cannot rollback in case of error: If there is an error during the migration, then you won’t be able to rollback. You would have to either rollback the migration or complete it manually. It’s a good idea to keep the operations executed inside a non-atomic migration to a minimum. If you have additional operations in the migration, move them to a new migration.
Migration might be vendor specific: The SQL generated by Django is specific to the database backend used in the project. It might work with other database backends, but that is not guaranteed. If you need to support multiple database backends, you need to make some adjustments to this approach.
数据库操作必须等效于状态操作:数据库和模型状态之间的不一致会引起很多麻烦。 一个很好的出发点是让Django在生成的业务在state_operations
的输出和编辑sqlmigrate
到使用database_operations
。
非原子迁移无法在发生错误的情况下回滚:如果在迁移过程中发生错误,则将无法回滚。 您将必须回滚迁移或手动完成迁移。 最好将非原子迁移内部执行的操作减到最少。 如果迁移中还有其他操作,请将其移至新迁移。
迁移可能是特定于供应商的: Django生成SQL特定于项目中使用的数据库后端。 它可能与其他数据库后端一起使用,但是不能保证。 如果需要支持多个数据库后端,则需要对该方法进行一些调整。
You started this tutorial with a large table and a problem. You wanted to make your app faster for your users, and you wanted to do that without causing them any downtime.
您从一张大桌子开始学习本教程,但遇到了问题。 您想使用户的应用程序更快,并且想要做到这一点而又不会导致他们的停机时间。
By the end of the tutorial, you managed to generate and safely modify a Django migration to achieve this goal. You tackled different problems along the way and managed to overcome them using built-in tools provided by the migrations framework.
在本教程结束时,您已成功生成并安全地修改了Django迁移以实现此目标。 您一路解决了各种问题,并使用迁移框架提供的内置工具设法解决了这些问题。
In this tutorial, you learned the following:
在本教程中,您学习了以下内容:
RunSQL
actionRunSQL
action reversibleRunSQL
操作在迁移中执行自定义SQL RunSQL
操作可逆 The separation between model and database state is an important concept. Once you understand it, and how to utilize it, you can overcome many limitations of the built-in migration operations. Some use cases that come to mind include adding an index that was already created in the database and providing vendor specific arguments to DDL commands.
模型和数据库状态之间的分离是一个重要的概念。 一旦了解了它以及如何使用它,就可以克服内置迁移操作的许多限制。 我想到的一些用例包括添加已经在数据库中创建的索引,以及为DDL命令提供供应商特定的参数。
翻译自: https://www.pybloggers.com/2019/04/how-to-create-an-index-in-django-without-downtime/
django不停机该程序