Aerich 迁移指南

Aerich 是 Tortoise ORM 的官方数据库迁移工具,功能类似 Django Migrations 或 Alembic,支持生成迁移文件、升级、回滚。 运行后会生成 aerich.ini 文件和 migrations/ 目录。 使用 pyproject.toml 可以避免项目根目录多出 aerich.ini,与现代 Python 项目工具链保持一致。当 aerich.ini 和 pyproject.toml 同时存在时,aerich.ini 优先。 Aerich 要求配置中的 apps 里必须包含 aerich.models,用于存储迁移版本

分享

官方文档:https://github.com/tortoise/aerich
适用版本:aerich 0.7+,tortoise-orm 0.20+(2026-05-08 核实)
最后更新:2026-04-11


一、安装与初始化

pip install aerich

Aerich 是 Tortoise ORM 的官方数据库迁移工具,功能类似 Django Migrations 或 Alembic,支持生成迁移文件、升级、回滚。

1.1 初始化项目

aerich init -t myapp.config.TORTOISE_ORM
参数 类型 默认值 说明
-t / --tortoise-orm str 无,必填 指向 TORTOISE_ORM 配置字典的 Python 路径
--location str ./migrations 迁移文件存放目录
-s / --src-folder str ./. 源码根目录(用于模块导入)

运行后会生成 aerich.ini 文件和 migrations/ 目录。


二、配置文件

2.1 aerich.ini

[aerich]
tortoise_orm = myapp.config.TORTOISE_ORM
location = ./migrations
src_folder = ./.
字段 说明
tortoise_orm TORTOISE_ORM 配置的 Python 模块路径
location 迁移文件目录
src_folder 模块搜索根目录

2.2 pyproject.toml 中配置(推荐)

[tool.aerich]
tortoise_orm = "myapp.config.TORTOISE_ORM"
location = "./migrations"
src_folder = "./."

使用 pyproject.toml 可以避免项目根目录多出 aerich.ini,与现代 Python 项目工具链保持一致。当 aerich.inipyproject.toml 同时存在时,aerich.ini 优先。


三、TORTOISE_ORM 配置要求

Aerich 要求配置中的 apps 里必须包含 aerich.models,用于存储迁移版本记录。

# myapp/config.py
TORTOISE_ORM = {
    "connections": {
        "default": "postgres://user:pass@localhost:5432/mydb"
    },
    "apps": {
        "models": {
            "models": ["myapp.models", "aerich.models"],  # aerich.models 必须包含
            "default_connection": "default",
        }
    },
    "use_tz": True,
    "timezone": "Asia/Shanghai",
}

四、迁移命令详解

4.1 aerich init-db

首次初始化数据库,创建所有表并生成初始迁移文件。只在项目初始化时运行一次。

aerich init-db
参数 类型 默认值 说明
--safe bool False 使用 CREATE TABLE IF NOT EXISTS,表已存在不报错

执行后会在 migrations/{app_name}/ 下生成类似 0_20260101000000_init.py 的文件,并在数据库中创建 aerich 表用于记录版本。

4.2 aerich migrate

检测模型变化,生成新的迁移文件(不执行到数据库)。

aerich migrate --name add_user_avatar
参数 类型 默认值 说明
--name str update 迁移文件名称后缀,建议使用描述性名称
--empty bool False 生成空迁移文件,用于手写自定义 SQL
--app str None 仅对指定 app 生成迁移(多 app 项目使用)

生成文件命名规则:{version}_{timestamp}_{name}.py,例如 1_20260101120000_add_user_avatar.py

4.3 aerich upgrade

将所有未应用的迁移文件执行到数据库。

aerich upgrade
参数 类型 默认值 说明
--app str None 仅升级指定 app 的迁移
-v / --version int None 升级到指定版本号(不指定则升级到最新)

4.4 aerich downgrade

回滚迁移到指定版本。

aerich downgrade -v 1
参数 类型 默认值 说明
-v / --version int 无,必填 回滚到的目标版本号
-d / --delete bool False 回滚后删除对应的迁移文件
--app str None 仅回滚指定 app 的迁移

4.5 aerich history

查看已应用的迁移历史记录。

aerich history
参数 类型 默认值 说明
--app str None 仅查看指定 app 的历史

输出示例:

1_20260101000000_init
2_20260110120000_add_user_avatar

4.6 aerich heads

查看当前最新版本(尚未应用到数据库的迁移头)。

aerich heads
参数 类型 默认值 说明
--app str None 仅查看指定 app 的 head

4.7 aerich inspectdb

从已有数据库逆向生成 Tortoise ORM 模型代码(适合接手旧项目)。

aerich inspectdb
aerich inspectdb --table users --table orders
参数 类型 默认值 说明
--table str(可多次指定) None 仅逆向指定表,不指定则逆向所有表

输出为标准输出,建议重定向到文件:

aerich inspectdb > myapp/models_generated.py

五、迁移文件结构

# migrations/models/1_20260101000000_init.py
from tortoise import BaseDBAsyncClient


async def upgrade(db: BaseDBAsyncClient) -> str:
    return """
        CREATE TABLE IF NOT EXISTS "user" (
            "id" SERIAL NOT NULL PRIMARY KEY,
            "username" VARCHAR(50) NOT NULL UNIQUE,
            "email" VARCHAR(255) NOT NULL,
            "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
        );
    """


async def downgrade(db: BaseDBAsyncClient) -> str:
    return """
        DROP TABLE IF EXISTS "user";
    """
  • upgrade:升级时执行的 SQL
  • downgrade:回滚时执行的 SQL
  • 每个函数返回 SQL 字符串(多条语句用分号分隔)或直接执行 db.execute_script(sql)

手写迁移时可以直接操作 db 对象:

async def upgrade(db: BaseDBAsyncClient) -> str:
    await db.execute_script("""
        UPDATE "user" SET email = LOWER(email);
        ALTER TABLE "user" ADD COLUMN "avatar_url" VARCHAR(500);
    """)
    return ""

六、多应用(Multi-App)迁移配置

当项目有多个独立模块时,可以在 apps 中配置多个应用,每个应用有独立的迁移目录。

TORTOISE_ORM = {
    "connections": {
        "default": "postgres://user:pass@localhost:5432/mydb"
    },
    "apps": {
        "users": {
            "models": ["myapp.users.models", "aerich.models"],
            "default_connection": "default",
        },
        "orders": {
            "models": ["myapp.orders.models"],
            "default_connection": "default",
        },
    },
}

迁移目录结构:

migrations/
  users/
    0_20260101000000_init.py
    1_20260110000000_add_avatar.py
  orders/
    0_20260101000000_init.py

对指定 app 运行迁移命令:

aerich migrate --app users --name add_avatar
aerich upgrade --app users
aerich downgrade --app orders -v 0

注意:aerich.models 只需要包含在一个 app 中,通常放在主 app 里。


七、FastAPI 集成示例

# main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from tortoise import Tortoise
from myapp.config import TORTOISE_ORM


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时初始化数据库连接
    # 迁移由 aerich upgrade 在部署时执行,不在这里执行
    await Tortoise.init(config=TORTOISE_ORM)
    yield
    # 关闭时释放连接
    await Tortoise.close_connections()


app = FastAPI(lifespan=lifespan)

在 FastAPI 中,lifespan 只负责初始化连接,不负责执行迁移。迁移应在部署流程中单独执行。


八、生产环境部署流程

8.1 Docker entrypoint 示例

#!/bin/bash
# entrypoint.sh
set -e

echo "Running database migrations..."
aerich upgrade

echo "Starting application..."
exec uvicorn myapp.main:app --host 0.0.0.0 --port 8000
# Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

8.2 标准部署步骤

# 1. 拉取最新代码
git pull origin main

# 2. 安装依赖
pip install -r requirements.txt

# 3. 执行迁移(生产环境一定要先备份数据库)
aerich upgrade

# 4. 重启服务
systemctl restart myapp

8.3 CI/CD 集成(GitHub Actions 示例)

- name: Run migrations
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
  run: |
    aerich upgrade

九、踩坑与注意事项

9.1 迁移文件冲突

场景:多人协作时,两个开发者同时基于同一版本生成了迁移文件,版本号相同。

现象aerich upgrade 时报错,提示版本冲突。

解决:手动将其中一个迁移文件的版本号调高,并修改文件名中的版本序号,确保迁移链不断。

# 查看当前 head
aerich heads

# 重命名冲突文件,修改版本号
mv migrations/models/2_xxx_feature_b.py migrations/models/3_xxx_feature_b.py
# 同时修改文件内部的版本引用(如有 depends_on 字段)

9.2 downgrade 数据丢失风险

downgrade 会执行迁移文件的 downgrade() 函数,通常包含 DROP COLUMNDROP TABLE 等破坏性操作。

  • 生产环境回滚前务必先备份数据库
  • 对于不可逆的迁移(如数据转换),downgrade() 函数可以返回空字符串并加注释说明
  • 建议在测试环境演练回滚流程
async def downgrade(db: BaseDBAsyncClient) -> str:
    # 此迁移不可逆(数据迁移),回滚前请手动恢复备份
    return ""

9.3 字段重命名需手动处理

Aerich 的自动检测基于字段名比对,无法区分"删除旧字段 + 新增新字段"和"重命名字段"。

错误做法:直接修改模型中的字段名,然后 aerich migrate,会生成先删除再新增的操作,导致数据丢失。

正确做法:生成空迁移后手动编写 SQL。

aerich migrate --name rename_username_to_name --empty
# 手动编写迁移内容
async def upgrade(db: BaseDBAsyncClient) -> str:
    return """
        ALTER TABLE "user" RENAME COLUMN "username" TO "name";
    """

async def downgrade(db: BaseDBAsyncClient) -> str:
    return """
        ALTER TABLE "user" RENAME COLUMN "name" TO "username";
    """

9.4 aerich.models 缺失报错

现象:运行 aerich init-dbaerich migrate 时报错 Table aerich does not exist

原因TORTOISE_ORMmodels 列表中没有包含 aerich.models

解决:在至少一个 app 的 models 列表中加入 "aerich.models"

9.5 多数据库环境下的迁移

每个数据库连接的迁移需要独立管理,使用 --app 参数指定。Aerich 的迁移版本记录(aerich 表)存储在对应 app 的 default_connection 数据库中。更多多数据库配置见 多数据库路由


最佳实践

始终用 aerich migrate 生成迁移文件,不要手动修改数据库:手动 ALTER TABLE 与 Aerich 迁移历史不同步,会导致后续迁移冲突甚至回滚失败。

迁移文件纳入版本控制migrations/ 目录必须提交到 Git,团队协作时每人拉取后运行 aerich upgrade 保持数据库同步,而非各自独立迁移。

生产环境迁移前备份数据库aerich upgrade 执行 DDL 语句,若迁移失败可能导致部分修改无法自动回滚(MySQL 的 DDL 不在事务中),迁移前手动备份是最后防线。

测试环境使用 generate_schemas=True,生产使用迁移Tortoise.init(generate_schemas=True) 适合测试中快速建表,生产环境必须用 Aerich 迁移管理,保证升降级路径清晰。

重命名字段用两步迁移:先添加新字段并迁移数据,再删除旧字段,而非直接重命名(Aerich autogenerate 无法区分重命名和删除+新增,直接生成的迁移会丢数据)。


常见陷阱

陷阱:aerich init-db 后数据库已有表,再 aerich migrate 冲突

现象: 新项目用 generate_schemas 建表后才引入 Aerich,aerich init-db 报表已存在。
原因: init-db 会执行 CREATE TABLE,但表已由 generate_schemas 创建。
解决: 在空数据库上运行 init-db;若表已存在,手动插入 aerich 版本记录(0001 → 初始状态)跳过 init,或删表重建。

陷阱:model 变更后未运行 aerich migrate 导致列不存在

现象: 代码中新增 model 字段后应用启动报 Unknown column 'xxx' in 'field list'
原因: Model 类已更新但数据库结构没有同步,缺少 aerich migrate && aerich upgrade 步骤。
解决: 建立开发流程:修改 Model → aerich migrate -n <desc> → 审查迁移文件 → aerich upgrade → 提交代码+迁移文件。

陷阱:多 app 时未指定 --app 导致迁移到错误数据库

现象: 在多数据库场景下,aerich migrate 将迁移应用到了错误的数据库。
原因: 未指定 --app 时 Aerich 默认操作第一个 app,多 app 环境需显式指定。
解决: 多 app 场景始终用 aerich migrate --app <app_name>aerich upgrade --app <app_name>


参见

初始化与配置
多数据库路由
模型字段完全参考

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog