You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker环境下Alembic/SQLAlchemy无法建表,报MissingGreenlet错误

问题:Docker环境执行Alembic迁移触发MissingGreenlet错误

非Docker环境运行程序完全正常,但执行docker-compose up --build时,出现以下错误:

sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?

相关代码

env.py

import asyncio
from logging.config import fileConfig

from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config

from alembic import context

from models import *
from config.settings import get_settings

config = context.config

config.set_section_option(
    config.config_ini_section, "sqlalchemy.url", get_settings().asyncpg_url
)

if config.config_file_name is not None:
    fileConfig(config.config_file_name)

target_metadata = Base.metadata

def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = get_settings().asyncpg_url
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

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

def do_run_migrations(connection: Connection) -> None:
    context.configure(connection=connection, target_metadata=target_metadata)

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

async def run_async_migrations() -> None:
    """In this scenario we need to create an Engine
    and associate a connection with the context.

    """

    connectable = async_engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)

    await connectable.dispose()

def run_migrations_online() -> None:
    """Run migrations in 'online' mode."""

    asyncio.run(run_async_migrations())

if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

docker-compose.yml

postgres:
  container_name: postgres
  image: 'postgres:16'
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 5s
    timeout: 5s
    retries: 5
  ports:
    - '5432:5432'
  env_file:
    - ./.env
  volumes:
    - './pgsql:/var/lib/postgresql/data'
  networks:
    - default

backend:
  container_name: backend
  build:
    context: .
    dockerfile: ./Dockerfile
  env_file:
    - .env
  ports:
    - "3000:80"
  links:
    - postgres
  depends_on:
    postgres:
      condition: service_healthy
  networks:
    - default

networks:
  default:
    driver: bridge

volumes:
  pgsql:
    driver: local

Dockerfile

FROM python:3.10

WORKDIR /app

COPY ./requirements.txt ./requirements.txt

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY . .

RUN if [ ! -d "/app/migrations" ]; then alembic init migrations; fi

RUN chmod 755 /app/start.sh

CMD ["sh", "start.sh"]

start.sh

#!/bin/sh
if [ ! -d "/app/migrations" ]; then
    alembic init migrations
fi

alembic upgrade head
uvicorn app.main:app --host 0.0.0.0 --port 80

解决方案

这个错误的核心原因是异步IO操作被意外地在同步上下文执行,结合你的场景,主要有两个修复方向:

1. 确保安装Greenlet依赖

SQLAlchemy的异步扩展依赖greenlet来管理协程上下文,本地环境可能已自动安装,但Docker镜像中可能缺失。

在requirements.txt中添加:

greenlet>=2.0.0

2. 修复配置获取的同步/异步冲突

如果你的get_settings()是异步函数(比如使用了Pydantic v2的异步Settings),那么在env.py的同步代码中直接调用会触发错误。需要修改env.py中获取配置的逻辑:

修改env.py开头的配置部分:

config = context.config

# 替换原有的配置设置代码
import asyncio
# 用asyncio.run在同步上下文执行异步配置获取
settings = asyncio.run(get_settings())
config.set_section_option(
    config.config_ini_section, "sqlalchemy.url", settings.asyncpg_url
)

同时修改run_migrations_offline()中的URL获取:

def run_migrations_offline() -> None:
    # ... 保留原有注释
    url = settings.asyncpg_url  # 改用已获取的settings
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )
    # ... 保留其余代码

3. 验证Alembic异步迁移逻辑

确保run_migrations_online()的实现符合SQLAlchemy异步迁移的规范,当前代码的写法是正确的,但需确保Greenlet已安装,否则asyncio.run无法正确处理SQLAlchemy的异步上下文。

完成以上修改后,重新构建Docker镜像并启动即可解决该错误。


内容的提问来源于stack exchange,提问作者Vog Prostit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:37:07