django-safe-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Django Migration — Zero Downtime

Django 迁移 — 零停机

Project Configuration

项目配置

Read the active host's project instruction file (for example,
AGENTS.md
or
CLAUDE.md
) for the values below. If they are not set, use the defaults shown.
KeyDefaultNotes
Django versionunknownAffects
db_default
availability — only Django 5.0+ supports it
Deploy strategyrolling deployRolling deploy is the most restrictive; blue/green or maintenance-window deploys allow more operations
Runtime guardnoneIf a guard is configured (e.g.
django-pg-zero-downtime-migrations
), operations it blocks are Errors; uncovered operations are Warnings
Migration command
python manage.py sqlmigrate
e.g.
make sqlmigrate
or
docker compose run web python manage.py sqlmigrate
Docs / wiki URLnoneIf set, append
#<anchor>
links when flagging issues in review output
To configure this skill for your project, add a section like this to the active host's project instruction file:
undefined
请读取当前宿主的项目说明文件(例如
AGENTS.md
或
CLAUDE.md
)获取以下配置值。如果未设置,则使用所示的默认值。
配置项默认值说明
Django 版本unknown影响
db_default
的可用性——仅 Django 5.0+ 支持该特性
部署策略rolling deploy滚动部署限制最严格;蓝绿部署或维护窗口部署允许更多操作
运行时防护none如果配置了防护工具(例如
django-pg-zero-downtime-migrations
),其阻止的操作将标记为错误;未覆盖的操作标记为警告
迁移命令
python manage.py sqlmigrate
例如
make sqlmigrate
或
docker compose run web python manage.py sqlmigrate
文档/维基 URLnone如果已设置,在审查输出中标记问题时需附加
#<锚点>
链接
要为你的项目配置此技能,请将以下类似内容添加到当前宿主的项目说明文件中:
undefined

django-safe-migration

django-safe-migration


---

---

When to Use

适用场景

  • "Review this migration"
  • "Is this migration safe?"
  • "Write a migration for..."
  • "Rewrite this migration to be safe"
  • "How do I add a NOT NULL column / drop a column / add an index / rename a column / add a FK..."

  • “审查这个迁移”
  • “这个迁移安全吗?”
  • “为…编写迁移”
  • “将此迁移重写为安全版本”
  • “如何添加NOT NULL列/删除列/添加索引/重命名列/添加FK…”

Why Zero-Downtime Migrations Matter

为什么零停机迁移很重要

In a rolling deploy, the new database schema is applied first, then application instances are restarted one by one. At any moment during the deploy, old code and new code run simultaneously against the same database.
Every migration must be safe to run while the previous version of the app is still serving traffic. A migration that takes an
ACCESS EXCLUSIVE
lock on a large table blocks all reads and writes — downtime even for a few seconds on a busy table.
The problem is not just the lock itself but the wait queue: a fast
ALTER TABLE
that takes 50ms will queue behind any long-running transaction, and all subsequent queries queue behind the migration. On a busy table this cascades into connection pool exhaustion.

在滚动部署中,首先应用新的数据库schema,然后逐个重启应用实例。在部署期间的任何时刻,旧代码和新代码都会同时针对同一个数据库运行。
在应用的旧版本仍在处理流量时,每个迁移都必须能够安全运行。对大表加
ACCESS EXCLUSIVE
锁的迁移会阻塞所有读写操作——即便只有几秒钟,对于繁忙的表来说也意味着停机。
问题不仅仅在于锁本身,还在于等待队列:一个耗时50毫秒的快速
ALTER TABLE
操作会排在任何长事务之后,而所有后续查询又会排在迁移之后。在繁忙的表上,这会级联导致连接池耗尽。

How PostgreSQL Locking Works

PostgreSQL 锁机制原理

LockAcquired byBlocks
ACCESS EXCLUSIVE
Most
ALTER TABLE
,
DROP INDEX
,
DROP CONSTRAINT
(FK)
All reads and writes
SHARE ROW EXCLUSIVE
ADD FOREIGN KEY
(on child table + referenced table simultaneously)
Writes only (on both tables)
SHARE
CREATE INDEX
Writes only
SHARE UPDATE EXCLUSIVE
CREATE INDEX CONCURRENTLY
,
VALIDATE CONSTRAINT
Nothing meaningful — safe under traffic
For the full conflict matrix (table-level locks × business logic operations × row-level locks) and the FIFO wait-queue explanation, load
references/postgres-locks.md
. Load it when:
  • explaining why a specific operation is unsafe
  • a developer asks what a lock type blocks or conflicts with
  • reasoning about whether two concurrent operations interact

锁类型触发操作阻塞内容
ACCESS EXCLUSIVE
大多数
ALTER TABLE
、
DROP INDEX
、
DROP CONSTRAINT
(FK)
所有读写操作
SHARE ROW EXCLUSIVE
ADD FOREIGN KEY
(同时对子表和被引用表加锁)
仅写操作(两张表均受影响)
SHARE
CREATE INDEX
仅写操作
SHARE UPDATE EXCLUSIVE
CREATE INDEX CONCURRENTLY
、
VALIDATE CONSTRAINT
无实质阻塞——流量下安全
完整的冲突矩阵(表级锁 × 业务逻辑操作 × 行级锁)以及FIFO等待队列的说明,请加载
references/postgres-locks.md
。在以下场景加载该文件:
  • 解释某个特定操作为什么不安全时
  • 开发者询问某类锁会阻塞或与什么冲突时
  • 推断两个并发操作是否会相互影响时

lock_timeout

lock_timeout

Any operation that requires
ACCESS EXCLUSIVE
should be preceded by
SET LOCAL lock_timeout
. This causes the migration to fail fast (with a clear error) instead of waiting indefinitely for the lock — preventing connection pool exhaustion from queue cascading.
For a normal transactional migration:
python
migrations.RunSQL("SET LOCAL lock_timeout = '2s'"),
migrations.AlterField(...),  # or any ACCESS EXCLUSIVE operation
SET LOCAL
scopes the timeout to the current transaction, so it does not affect other sessions or persist after the migration completes.
Default:
2s
— adjust up if the table is known to have long-running transactions that legitimately need more time, or down for stricter environments.
For
atomic = False
migrations, combine
SET LOCAL
and the DDL in the same
RunSQL
operation; see Structural Rules below.

任何需要
ACCESS EXCLUSIVE
锁的操作都应该在之前执行
SET LOCAL lock_timeout
。这会让迁移快速失败(并返回明确的错误),而不是无限期等待锁——从而防止队列级联导致的连接池耗尽。
对于普通的事务性迁移:
python
migrations.RunSQL("SET LOCAL lock_timeout = '2s'"),
migrations.AlterField(...),  # or any ACCESS EXCLUSIVE operation
SET LOCAL
将超时范围限制在当前事务内,因此不会影响其他会话,也不会在迁移完成后持续生效。
默认值:
2s
——如果已知表上存在确实需要更多时间的长事务,可以调大该值;对于更严格的环境,可以调小。
对于
atomic = False
的迁移,请将
SET LOCAL
和DDL操作合并在同一个
RunSQL
操作中;参见下文的结构规则。

Key Patterns

核心模式

NOT VALID + VALIDATE (for FK and CHECK constraints)

NOT VALID + VALIDATE(适用于FK和CHECK约束)

A two-step PostgreSQL technique to add a constraint without a long lock:
  1. ADD CONSTRAINT … NOT VALID
    — creates the constraint and enforces it on new writes immediately, but skips scanning existing rows. Takes a brief lock with no table scan —
    SHARE ROW EXCLUSIVE
    on both tables for FK constraints,
    ACCESS EXCLUSIVE
    for CHECK constraints.
  2. VALIDATE CONSTRAINT
    — scans existing rows to confirm they satisfy the constraint. Takes
    SHARE UPDATE EXCLUSIVE
    (plus
    ROW SHARE
    on the referenced table for FK constraints), which does not block reads or writes.
The dangerous part is the full-table scan, not the constraint creation itself. Splitting it keeps the write-blocking lock window to milliseconds, with the long scan moved to a non-blocking step.
PostgreSQL docs:
NOT VALID
·
VALIDATE CONSTRAINT

这是PostgreSQL的一种分两步添加约束的技术,可避免长时间加锁:
  1. ADD CONSTRAINT … NOT VALID
    — 创建约束并立即对新写入强制执行,但跳过扫描现有行。仅需短暂加锁,无需全表扫描——FK约束会在两张表上加
    SHARE ROW EXCLUSIVE
    锁,CHECK约束则加
    ACCESS EXCLUSIVE
    锁。
  2. VALIDATE CONSTRAINT
    — 扫描现有行以确认它们满足约束。仅需
    SHARE UPDATE EXCLUSIVE
    锁(对于FK约束,还会在被引用表上加
    ROW SHARE
    锁),不会阻塞读写操作。
危险的部分是全表扫描,而不是约束创建本身。拆分后,阻塞写操作的锁窗口被缩短到毫秒级,而耗时较长的扫描被移到了非阻塞步骤中。
PostgreSQL 文档:
NOT VALID
·
VALIDATE CONSTRAINT

Runtime Guards

运行时防护工具

Some projects configure a custom database backend or linter that raises errors for unsafe operations at migration time (e.g.
zero_downtime_migrations
,
django-pg-zero-downtime-migrations
,
django-migration-linter
).
When reviewing a migration:
  • Operations the project's runtime guard blocks → classify as Error (migration will not run)
  • Operations the guard does not cover → classify as Warning (migration runs but may cause downtime)
If no runtime guard is configured, treat all unsafe operations as Errors that require a safe rewrite before deploying to production.
Check the Project Configuration block above (or
AGENTS.md
) for what this project's guard covers.

有些项目会配置自定义数据库后端或检查工具,在迁移时对不安全的操作抛出错误(例如
zero_downtime_migrations
、
django-pg-zero-downtime-migrations
、
django-migration-linter
)。
审查迁移时:
  • 项目运行时防护工具阻止的操作 → 归类为错误(迁移无法运行)
  • 防护工具未覆盖的操作 → 归类为警告(迁移可以运行,但可能导致停机)
如果未配置运行时防护工具,将所有不安全的操作都视为错误,需要在部署到生产环境前进行安全重写。
请查看上方的项目配置模块(或
AGENTS.md
),了解此项目的防护工具覆盖范围。

Mode 1: Review

模式1:审查

Goal

目标

Identify every operation that is unsafe or risky for a rolling deploy on PostgreSQL.
找出在PostgreSQL上进行滚动部署时所有不安全或有风险的操作。

Steps

步骤

  1. Read the migration file in full.
  2. Run
    <migration_command> <app_label> <migration_name>
    to get the actual SQL Django will execute. Always do this — the generated SQL is the ground truth. Use the
    Migration command
    value from the active host's project instruction file; default is
    python manage.py sqlmigrate
    . If the key is not set and a
    Makefile
    or
    docker-compose.yml
    exists in the project root, ask the user: "How do you run sqlmigrate in this project?" and suggest they save the answer to that instruction file. The ORM operation class alone is not sufficient: for example,
    AlterField
    on a FK field that adds
    null=True
    also drops and re-adds the FK constraint, emitting
    DROP CONSTRAINT
    (taking
    ACCESS EXCLUSIVE
    on both the child and referenced table) followed by
    ADD CONSTRAINT FOREIGN KEY
    without
    NOT VALID
    (taking
    SHARE ROW EXCLUSIVE
    with a full scan on both tables) — neither is visible from the migration file alone.
  3. Load
    references/operation-guide.md
    . Load
    references/postgres-locks.md
    when explaining why a flagged operation is unsafe.
  4. For each SQL statement produced, check it against the detection checklist below.
  5. If any operation requires
    ACCESS EXCLUSIVE
    (flagged as error or warning), ask the user before outputting the report:
    "This migration contains an
    ACCESS EXCLUSIVE
    operation. A
    lock_timeout
    should be added to fail fast instead of queuing and cascading. The default is
    2s
    — confirm or provide a custom value." If the migration already contains
    SET LOCAL lock_timeout
    , note the existing value and ask the user to confirm it is appropriate. Use the confirmed value in the fix instructions.
  6. Output a structured report:
  1. 完整读取迁移文件。
  2. 运行
    <migration_command> <app_label> <migration_name>
    获取Django实际执行的SQL。务必执行此步骤——生成的SQL是最终依据。 请使用当前宿主项目说明文件中的
    迁移命令
    值;默认值为
    python manage.py sqlmigrate
    。如果未设置该配置项,且项目根目录中存在
    Makefile
    或
    docker-compose.yml
    ,请询问用户:“你在这个项目中如何运行sqlmigrate?”并建议他们将答案保存到该说明文件中。仅靠ORM操作类是不够的:例如,对FK字段执行
    AlterField
    添加
    null=True
    时,还会删除并重新添加FK约束,生成
    DROP CONSTRAINT
    (对子表和被引用表都加
    ACCESS EXCLUSIVE
    锁),随后是不带
    NOT VALID
    的
    ADD CONSTRAINT FOREIGN KEY
    (对两张表都加
    SHARE ROW EXCLUSIVE
    锁并进行全表扫描)——这两个操作仅从迁移文件中是看不到的。
  3. 加载
    references/operation-guide.md
    。在解释被标记的操作为什么不安全时,加载
    references/postgres-locks.md
    。
  4. 针对生成的每条SQL语句,对照下方的检测清单进行检查。
  5. 如果有任何操作需要
    ACCESS EXCLUSIVE
    锁(标记为错误或警告),在输出报告前询问用户:
    “此迁移包含
    ACCESS EXCLUSIVE
    操作。应添加
    lock_timeout
    以实现快速失败,避免队列级联。默认值为
    2s
    ——请确认或提供自定义值。” 如果迁移中已经包含
    SET LOCAL lock_timeout
    ,请说明现有值并询问用户确认是否合适。 在修复说明中使用确认后的值。
  6. 输出结构化报告:

Migration Review: <filename>

迁移审查:<filename>

Errors — will cause downtime or be blocked by runtime guard

错误 — 会导致停机或被运行时防护工具阻止

  • [ERROR]
    <OperationClass>
    on
    <app>.<Model>.<field>
    : <what it does and why it's unsafe> Fix: <one-sentence description of the safe alternative> What this rewrite changes: clarify whether
    ACCESS EXCLUSIVE
    is still required in the safe version, and if so, explain what actually improves — lock duration (full table scan → milliseconds), failure mode (silent queue cascade → fast timeout error), or both. Never let a reader assume the rewrite eliminates the lock entirely. <wiki/docs link if configured>
  • [错误]
    <app>.<Model>.<field>
    上的
    <OperationClass>
    :<操作内容及不安全原因> 修复方案:<安全替代方案的一句话描述> 重写变更说明:说明安全版本中是否仍需要
    ACCESS EXCLUSIVE
    锁,如果需要,解释实际改进点——锁持续时间(全表扫描 → 毫秒级)、失败模式(静默队列级联 → 快速超时错误),或两者皆有。绝不能让读者误以为重写完全消除了锁。 <如果配置了维基/文档链接,附上>

Warnings — may cause extended locks or deployment issues

警告 — 可能导致长时间锁或部署问题

  • [WARNING]
    <OperationClass>
    : <issue> Fix: <safe alternative>
  • [警告]
    <OperationClass>
    :<问题> 修复方案:<安全替代方案>

Structural Issues

结构问题

  • [ERROR|WARNING] <issue> (e.g., missing atomic=False, RunPython without reverse)
  • [错误|警告] <问题>(例如缺少 atomic=False、RunPython 没有反向操作)

Safe

安全操作

  • [OK] <operations that pass all checks>
  1. If there are errors or warnings, offer to rewrite (Mode 3).
  2. If everything passes, confirm the migration is safe to deploy.

  • [通过] <通过所有检查的操作>
  1. 如果存在错误或警告,主动提供重写服务(模式3)。
  2. 如果所有检查都通过,确认迁移可以安全部署。

Mode 2: Write

模式2:编写

Goal

目标

Generate correct, zero-downtime migration(s) for a described change.
为描述的变更生成正确的零停机迁移。

Steps

步骤

  1. Load
    references/operation-guide.md
    and
    references/examples.md
    .
  2. Clarify the operation if ambiguous:
    • What model and field?
    • New column or changing an existing one?
    • For FK: which table is referenced? New column or existing?
    • For type changes: from what type to what type?
    • Django version (affects
      db_default
      availability)?
  3. If the operation will require
    ACCESS EXCLUSIVE
    , ask the user:
    "This migration will use
    ACCESS EXCLUSIVE
    . A
    lock_timeout
    will be included to fail fast if the lock cannot be acquired. The default is
    2s
    — confirm or provide a custom value."
  4. Determine how many migration files are needed — many patterns require two files in separate PRs.
  5. Generate the migration file(s) using patterns from
    references/examples.md
    . Add
    SET LOCAL lock_timeout = '<confirmed_value>'
    before each
    ACCESS EXCLUSIVE
    statement. In normal
    atomic = True
    migrations, this can be a preceding
    RunSQL
    ; in
    atomic = False
    migrations, combine the timeout and DDL in the same
    RunSQL
    operation so
    SET LOCAL
    is still active when the DDL runs.
  6. When two files are needed, always output deployment instructions:
undefined
  1. 加载
    references/operation-guide.md
    和
    references/examples.md
    。
  2. 如果操作不明确,先进行澄清:
    • 涉及哪个模型和字段?
    • 是新列还是修改现有列?
    • 对于FK:引用哪张表?是新列还是现有列?
    • 对于类型变更:从什么类型改成什么类型?
    • Django版本(影响
      db_default
      的可用性)?
  3. 如果操作需要
    ACCESS EXCLUSIVE
    锁,询问用户:
    “此迁移将使用
    ACCESS EXCLUSIVE
    锁。将包含
    lock_timeout
    以便在无法获取锁时快速失败。默认值为
    2s
    ——请确认或提供自定义值。”
  4. 确定需要多少个迁移文件——许多模式需要分两个PR提交两个文件。
  5. 使用
    references/examples.md
    中的模式生成迁移文件。在每个
    ACCESS EXCLUSIVE
    语句前添加
    SET LOCAL lock_timeout = '<确认后的值>'
    。在普通的
    atomic = True
    迁移中,可以用一个前置的
    RunSQL
    实现;在
    atomic = False
    迁移中,请将超时设置和DDL合并在同一个
    RunSQL
    操作中,确保DDL运行时
    SET LOCAL
    仍然生效。
  6. 如果需要两个文件,始终输出部署说明:
undefined

Deployment Order

Deployment Order

  • Migration 1: ships in the same PR as the model/code change
  • Migration 2: ships in a follow-up PR after the deploy is confirmed stable

---
  • Migration 1: ships in the same PR as the model/code change
  • Migration 2: ships in a follow-up PR after the deploy is confirmed stable

---

Mode 3: Rewrite

模式3:重写

Goal

目标

Transform an existing unsafe migration into one or more safe migrations.
将现有的不安全迁移转换为一个或多个安全迁移。

Steps

步骤

  1. Read the migration file.
  2. Identify all unsafe operations using the detection checklist.
  3. Load
    references/operation-guide.md
    and
    references/examples.md
    .
  4. If any operation requires
    ACCESS EXCLUSIVE
    , ask the user:
    "This migration contains an
    ACCESS EXCLUSIVE
    operation. A
    lock_timeout
    will be added to the rewrite to fail fast if the lock cannot be acquired. The default is
    2s
    — confirm or provide a custom value."
  5. For each unsafe operation, apply the correct safe pattern.
  6. Add
    SET LOCAL lock_timeout = '<confirmed_value>'
    before each
    ACCESS EXCLUSIVE
    statement in the rewritten migration. In normal
    atomic = True
    migrations, this can be a preceding
    RunSQL
    ; in
    atomic = False
    migrations, combine the timeout and DDL in the same
    RunSQL
    operation so
    SET LOCAL
    is still active when the DDL runs.
  7. If the rewrite requires splitting into two files, generate both and include deployment instructions.
  8. Preserve: migration number prefix,
    dependencies
    , any safe operations unchanged.
  9. After rewriting, verify structural rules (see below).
  10. After the migration code, always output a "What this rewrite changes" block that explains:
    • Whether
      ACCESS EXCLUSIVE
      is still required (often yes — be explicit about this).
    • What actually improves: lock duration (full table scan → milliseconds for metadata-only ops), failure mode (silent queue → fast timeout error with
      lock_timeout
      ), or both.
    • Any data integrity window introduced (e.g.,
      NOT VALID
      means existing rows are unvalidated until Migration 2 runs) and whether it matters given the table's prior state.

  1. 读取迁移文件。
  2. 使用检测清单找出所有不安全的操作。
  3. 加载
    references/operation-guide.md
    和
    references/examples.md
    。
  4. 如果有任何操作需要
    ACCESS EXCLUSIVE
    锁,询问用户:
    “此迁移包含
    ACCESS EXCLUSIVE
    操作。重写时将添加
    lock_timeout
    以便在无法获取锁时快速失败。默认值为
    2s
    ——请确认或提供自定义值。”
  5. 对每个不安全的操作,应用正确的安全模式。
  6. 在重写后的迁移中,每个
    ACCESS EXCLUSIVE
    语句前都添加
    SET LOCAL lock_timeout = '<确认后的值>'
    。在普通的
    atomic = True
    迁移中,可以用一个前置的
    RunSQL
    实现;在
    atomic = False
    迁移中,请将超时设置和DDL合并在同一个
    RunSQL
    操作中,确保DDL运行时
    SET LOCAL
    仍然生效。
  7. 如果重写需要拆分为两个文件,生成两个文件并包含部署说明。
  8. 保留以下内容:迁移编号前缀、
    dependencies
    、所有安全操作保持不变。
  9. 重写完成后,验证结构规则(见下文)。
  10. 在迁移代码之后,始终输出**“重写变更说明”**模块,解释以下内容:
    • 是否仍需要
      ACCESS EXCLUSIVE
      锁(通常是需要的——请明确说明这一点)。
    • 实际改进点:锁持续时间(全表扫描 → 仅元数据操作的毫秒级)、失败模式(静默队列 → 带
      lock_timeout
      的快速超时错误),或两者皆有。
    • 引入的任何数据完整性窗口(例如
      NOT VALID
      意味着在迁移2运行前,现有行未经过验证),以及考虑到表的先前状态,这是否有影响。

Detection Checklist

检测清单

Errors — unsafe regardless of runtime guard

错误 — 无论是否有运行时防护都不安全

Django operationWhat to detectWhy it's unsafe
AddField
null=False
and no
db_default
(Django 5.0+)
Old code inserts omit the column — no DB-level default to fall back on
RemoveField
not inside
SeparateDatabaseAndState
Old code queries the dropped column by name — crashes immediately
DeleteModel
not inside
SeparateDatabaseAndState
Old code queries the dropped table — crashes immediately
AddIndex
not using
AddIndexConcurrently
CREATE INDEX
takes
SHARE
lock — blocks writes during build
RemoveIndex
not using
RemoveIndexConcurrently
DROP INDEX
takes
ACCESS EXCLUSIVE
— blocks reads and writes
AddConstraint
(FK)
not using
NOT VALID + VALIDATE
pattern
Full table scan under
SHARE ROW EXCLUSIVE
on both the child and referenced table — blocks writes (not reads) for the scan duration
AddConstraint
(CHECK)
not using
NOT VALID + VALIDATE
pattern
Full table scan under
ACCESS EXCLUSIVE
AlterField
removing
null=True
on existing column without CHECK path
Full table scan under
ACCESS EXCLUSIVE
AlterField
(FK field)
sqlmigrate
output contains
DROP CONSTRAINT
followed by
ADD CONSTRAINT FOREIGN KEY
Django drops and re-adds the FK:
DROP CONSTRAINT
takes
ACCESS EXCLUSIVE
on both the child and referenced table; re-adding without
NOT VALID
takes
SHARE ROW EXCLUSIVE
with a full scan on both tables. Use
SeparateDatabaseAndState
with
RunSQL
to add
NOT VALID
and include
lock_timeout
.
AddIndexConcurrently
or
RemoveIndexConcurrently
atomic
not
False
on
Migration
class
CONCURRENTLY cannot run inside a transaction — will error
RunPython
function imports model directly (
from app.models import X
)
Uses current model class, not historical snapshot — breaks old migrations
Django操作检测点不安全原因
AddField
null=False
且无
db_default
(Django 5.0+)
旧代码的插入操作会省略该列——没有数据库级默认值可以兜底
RemoveField
未包含在
SeparateDatabaseAndState
中
旧代码按名称查询已删除的列——会立即崩溃
DeleteModel
未包含在
SeparateDatabaseAndState
中
旧代码查询已删除的表——会立即崩溃
AddIndex
未使用
AddIndexConcurrently
CREATE INDEX
会加
SHARE
锁——构建期间阻塞写操作
RemoveIndex
未使用
RemoveIndexConcurrently
DROP INDEX
会加
ACCESS EXCLUSIVE
锁——阻塞所有读写操作
AddConstraint
(FK)
未使用
NOT VALID + VALIDATE
模式
对子表和被引用表都加
SHARE ROW EXCLUSIVE
锁并进行全表扫描——扫描期间阻塞写操作(不阻塞读)
AddConstraint
(CHECK)
未使用
NOT VALID + VALIDATE
模式
在
ACCESS EXCLUSIVE
锁下进行全表扫描
AlterField
未通过CHECK路径移除现有列的
null=True
在
ACCESS EXCLUSIVE
锁下进行全表扫描
AlterField
(FK字段)
sqlmigrate
输出包含
DROP CONSTRAINT
后跟
ADD CONSTRAINT FOREIGN KEY
Django会删除并重新添加FK:
DROP CONSTRAINT
对子表和被引用表都加
ACCESS EXCLUSIVE
锁;不带
NOT VALID
的重新添加会对两张表都加
SHARE ROW EXCLUSIVE
锁并进行全表扫描。请使用带
RunSQL
的
SeparateDatabaseAndState
添加
NOT VALID
并包含
lock_timeout
。
AddIndexConcurrently
或
RemoveIndexConcurrently
Migration
类的
atomic
不为
False
CONCURRENTLY 不能在事务内运行——会报错
RunPython
函数直接导入模型(
from app.models import X
)
使用当前模型类而非历史快照——会破坏旧迁移

Additional errors if covered by the project's runtime guard

如果项目运行时防护覆盖,需额外标记的错误

If the project has a runtime guard configured (see Project Configuration), also flag these as Errors (they will raise at migration time):
Django operationWhat to detect
RenameField
any rename
RenameModel
any rename
AlterField
column type changes
AddConstraint
ExclusionConstraint
If no runtime guard is configured, flag these as Errors too — they require a safe rewrite.
如果项目配置了运行时防护工具(见项目配置),也需将以下内容标记为错误(它们会在迁移时抛出异常):
Django操作检测点
RenameField
任何重命名操作
RenameModel
任何重命名操作
AlterField
列类型变更
AddConstraint
ExclusionConstraint
如果未配置运行时防护工具,也需将这些标记为错误——它们需要进行安全重写。

Warnings

警告

Django operationWhat to detectIssue
AddConstraint
(UNIQUE)
not using index-first patternInline index under
SHARE
lock — blocks writes during build
AlterField
adding
null=True
to an existing column (
DROP NOT NULL
)
Takes
ACCESS EXCLUSIVE
— fast catalog update, but queues behind any long-running transaction; all subsequent queries queue behind the migration and can exhaust the connection pool
RunPython
missing
reverse_code
Not reversible
RunSQL
missing
reverse_sql
Not reversible

Django操作检测点问题
AddConstraint
(UNIQUE)
未使用先建索引模式在
SHARE
锁下内联建索引——构建期间阻塞写操作
AlterField
给现有列添加
null=True
(
DROP NOT NULL
)
会加
ACCESS EXCLUSIVE
锁——虽然是快速的系统目录更新,但会排在任何长事务之后;所有后续查询都会排在迁移之后,可能耗尽连接池
RunPython
缺少
reverse_code
不可回滚
RunSQL
缺少
reverse_sql
不可回滚

Structural Rules (always check)

结构规则(始终检查)

  • atomic = False
    on the
    Migration
    class whenever any operation uses
    CONCURRENTLY
    or
    VALIDATE CONSTRAINT
    . For
    VALIDATE CONSTRAINT
    specifically: (1)
    SHARE UPDATE EXCLUSIVE
    is self-conflicting — see the conflict matrix in
    references/postgres-locks.md
    ; (2)
    atomic = True
    keeps the wrapping transaction open for the full scan duration, holding all prior statement locks and increasing deadlock risk; (3)
    atomic = False
    lets VALIDATE run in its own transaction and release locks immediately on completion.
  • SeparateDatabaseAndState
    split for
    RemoveField
    /
    DeleteModel
    : always two separate files.
  • RunPython
    : always provide
    reverse_code=migrations.RunPython.noop
    at minimum.
  • RunSQL
    : always provide
    reverse_sql
    . If truly irreversible, use
    migrations.RunSQL.noop
    and note why.
  • Model access in
    RunPython
    : always
    apps.get_model("app", "ModelName")
    , never direct imports.
  • SET LOCAL lock_timeout
    : required before every
    ACCESS EXCLUSIVE
    operation. Default value is
    2s
    ; always confirm with the user before writing or rewriting. Use
    SET LOCAL
    (not
    SET
    ) so the timeout is scoped to the current transaction.
    • atomic = False
      exception
      :
      SET LOCAL
      only resets at transaction end. In an
      atomic = False
      migration, each operation runs outside a wrapping transaction, so a standalone
      migrations.RunSQL("SET LOCAL lock_timeout = '2s'")
      resets before the next operation executes and has no effect. When the migration has
      atomic = False
      , always combine the timeout and the DDL in a single
      RunSQL
      call:
      python
      migrations.RunSQL("""
          SET LOCAL lock_timeout = '2s';
          ALTER TABLE app_model ADD COLUMN ...;
      """)
  • 只要有任何操作使用
    CONCURRENTLY
    或
    VALIDATE CONSTRAINT
    ,
    Migration
    类必须设置
    atomic = False
    。特别是对于
    VALIDATE CONSTRAINT
    :(1)
    SHARE UPDATE EXCLUSIVE
    锁是自冲突的——参见
    references/postgres-locks.md
    中的冲突矩阵;(2)
    atomic = True
    会让包装事务在整个扫描期间保持打开状态,持有所有先前语句的锁,增加死锁风险;(3)
    atomic = False
    让VALIDATE在自己的事务中运行,完成后立即释放锁。
  • RemoveField
    /
    DeleteModel
    的
    SeparateDatabaseAndState
    拆分:始终分为两个独立文件。
  • RunPython
    :至少始终提供
    reverse_code=migrations.RunPython.noop
    。
  • RunSQL
    :始终提供
    reverse_sql
    。如果确实不可回滚,使用
    migrations.RunSQL.noop
    并说明原因。
  • RunPython
    中的模型访问:始终使用
    apps.get_model("app", "ModelName")
    ,绝对不要直接导入。
  • SET LOCAL lock_timeout
    :每个
    ACCESS EXCLUSIVE
    操作前都必须添加。默认值为
    2s
    ;编写或重写前务必与用户确认。使用
    SET LOCAL
    (而非
    SET
    ),确保超时范围限定在当前事务内。
    • atomic = False
      例外情况
      :
      SET LOCAL
      仅在事务结束时重置。在
      atomic = False
      的迁移中,每个操作都在包装事务之外运行,因此单独的
      migrations.RunSQL("SET LOCAL lock_timeout = '2s'")
      会在下一个操作执行前重置,不会生效。当迁移设置了
      atomic = False
      时,始终将超时设置和DDL合并在同一个
      RunSQL
      调用中:
      python
      migrations.RunSQL("""
          SET LOCAL lock_timeout = '2s';
          ALTER TABLE app_model ADD COLUMN ...;
      """)