PAYATHON 2026

为什么 Alembic 在生产环境正常,却没有为 Docker 中的数据库建表?

数据小许

结论:正常退出不代表执行了建表操作

现有日志不足以确定唯一原因。应先核对 env.py 的连接来源,确认 Alembic 实际连接的数据库,再对比容器内的 heads、history 和 current 输出,检查可见的迁移和数据库记录的当前 revision。这些信息有助于区分连接错误、迁移文件缺失和版本状态不一致。

alembic upgrade head 会执行已有迁移文件中尚未应用的 upgrade(),不会自动根据 SQLAlchemy 或 SQLModel 模型生成表结构。如果没有可执行的迁移、版本已经到达 head,或者连接到了另一个数据库,都可能出现退出码为 0、目标库却没有业务表的情况。

日志中有哪些线索?

迁移服务完成依赖安装后,只出现了:

INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
migrate-1 exited with code 0

这些日志说明 Alembic 配置了 PostgreSQL 迁移上下文,进程没有报告错误。提供的日志中没有出现常见的 Running upgrade ... 记录,不能仅凭这些信息认定建表迁移已经执行。

PostgreSQL 日志中还有一条记录:

PostgreSQL Database directory appears to contain a database; Skipping initialization

这说明 postgres_data 中已有数据库数据,本次启动复用了原来的数据库状态。它不能说明业务表存在,也不能说明 Alembic 的版本记录正确。

pg_isready 检查数据库是否接受连接。condition: service_healthy 可以控制迁移服务的启动时机,但不会检查表结构是否完整。

1. 确认 Alembic 使用了 Compose 中的连接地址

配置中设置了:

TRAVELER_DB_HOST: postgresql+psycopg://traveler:traveler@postgres:5432/traveler

这个地址适用于同一 Compose 网络内的 migrate 服务。Alembic 不会自动识别名为 TRAVELER_DB_HOST 的环境变量,必须由项目配置代码或 env.py 读取它。

检查 traveler/db/alembic.ini 和实际使用的 env.py,重点确认:

  • 连接最终来自 sqlalchemy.url,还是项目自己的配置对象。
  • TRAVELER_DB_HOST 在代码中代表完整 URL,还是仅代表主机名。当前传入的是完整 URL。
  • 是否存在默认连接地址或其他配置覆盖了这个环境变量。

如果项目决定让该变量保存完整 URL,同步迁移的连接部分可以写成下面这样。示例中的 target_metadata 需要沿用项目实际配置,这段代码不能直接作为完整的 env.py 使用:

import os

from alembic import context
from sqlalchemy import create_engine, pool


def run_migrations_online():
    engine = create_engine(
        os.environ["TRAVELER_DB_HOST"],
        poolclass=pool.NullPool,
    )

    with engine.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
        )

        with context.begin_transaction():
            context.run_migrations()

同时确认在线执行分支确实调用了 run_migrations_online()。如果现有 env.py 已有正确的连接和事务逻辑,只修正配置来源即可。

从宿主机上的数据库客户端连接时,使用映射端口对应的 localhost:5432;从 migrate 容器连接时,使用服务名 postgres:5432。两端都应选择数据库 traveler。

2. 在迁移容器环境中检查版本和迁移文件

migrate 已退出,可以用一次性容器执行检查。下面的命令保留原有的依赖安装步骤,并覆盖服务启动命令:

docker compose run --rm migrate bash -lc '
  pip install --no-cache-dir -e . &&
  alembic -c traveler/db/alembic.ini heads --verbose &&
  alembic -c traveler/db/alembic.ini history --verbose &&
  alembic -c traveler/db/alembic.ini current --verbose
'

这三个命令分别用于检查:

命令检查内容
heads --verbose当前可见迁移脚本的末端 revision
history --verbose迁移链及脚本位置
current --verbose按当前连接配置读取的数据库 revision

根据结果继续判断:

  • heads 和 history 没有列出迁移:检查 script_location、可能配置的 version_locations,以及对应文件在 /app 中是否存在。./:/app 会挂载宿主机目录,该目录必须包含迁移脚本。
  • 数据库当前 revision 已等于 head:Alembic 不会重复执行旧迁移。需要继续确认是否连错数据库、查看了错误的 schema,或曾经只标记版本而没有执行 DDL。
  • 数据库没有当前 revision,但有完整迁移链:确认连接目标后执行升级,并检查是否出现实际迁移记录。若仍无操作,应检查 env.py 是否调用了 context.run_migrations()。

current 查询的也是 Alembic 自己配置的数据库,因此必须结合前一步的连接核对结果一起看。

3. 检查建表操作是否存在于迁移脚本中

找到迁移文件,查看其中的 upgrade()。负责建立初始结构的迁移应包含相应的 op.create_table(...),或其他明确执行建表的操作。如果迁移函数只有 pass,即使升级成功也不会创建业务表。

如果项目还没有初始建表迁移,应先确保模型已注册到 metadata,再针对确认无误的开发数据库生成迁移:

alembic -c traveler/db/alembic.ini revision --autogenerate -m "create initial tables"

对于 SQLModel 项目,通常需要设置:

from sqlmodel import SQLModel

# 设置前,需要导入定义数据库表的模型模块。
target_metadata = SQLModel.metadata

只导入 SQLModel 不会自动加载项目中的所有模型。如果表模型模块没有被导入,metadata 可能缺少表定义,自动生成的迁移也可能不符合预期。

生成后必须检查 upgrade() 和 downgrade(),确认包含预期的建表操作、没有意外的删除操作,再执行:

alembic -c traveler/db/alembic.ini upgrade head

如果生产环境已经有完整迁移历史,应优先找回并使用这套迁移文件,避免另建一套互不衔接的初始迁移。自动生成迁移属于开发步骤,不宜放进每次容器启动流程。

4. 直接检查容器数据库中的表和 schema

从 PostgreSQL 服务内部连接,可以先排除数据库客户端连错实例的问题:

docker compose exec postgres psql -U traveler -d traveler

然后执行:

SELECT current_database(), current_user, current_schema();
SHOW search_path;

SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY table_schema, table_name;

这可以帮助确认业务表是否位于客户端未展开的其他 schema 中,以及是否存在 alembic_version 表。如果使用默认版本表名且它位于 public,再查询:

SELECT version_num FROM public.alembic_version;

如果配置了自定义版本表名或 version_table_schema,应按实际配置查询。

假如版本表已经记录 head,但预期业务表不存在,需要调查是否执行过 stamp head、手动删表,或者应用过空迁移。stamp 只修改迁移版本记录,不会执行建表操作,不能用它修复缺失的表。

5. 核实持久化数据卷的历史状态

当前数据库使用命名卷:

volumes:
  - postgres_data:/var/lib/postgresql/data

重启或重建容器会继续使用卷中的数据库。对于已有数据目录,修改 POSTGRES_DB、POSTGRES_USER 或 POSTGRES_PASSWORD 不会重新初始化数据库。

确认开发数据库中的数据可以丢弃后,可以重建对应数据卷,再用完整迁移链初始化。需要保留数据时,应备份并制定修复迁移。不要一开始就删除卷,这会同时丢失用于排查的版本记录和现有数据。

题目中的 Compose 代码缩进看起来在粘贴时丢失了,实际 YAML 需要正确缩进。不过日志已经显示两个服务都被启动,不能仅凭展示格式认定这就是本次故障原因。

备注:内容仅供参考。