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

FastApi结合Tortoise-ORM/SQLAlchemy查询数据阻塞异步IO问题

问题原因分析及解决方案

核心原因

你遇到的阻塞问题,本质是asyncio单线程事件循环被同步CPU密集型操作占用,而非数据库异步查询本身的问题。

具体原因拆解

  • Pydantic同步序列化阻塞事件循环:
    不管是await ServerPydantic.from_queryset(Server.all())还是SQLAlchemy异步查询后的模型转换,ORM查询完成后,将25000条数据库对象转换为Pydantic模型的过程是纯同步CPU操作——这个过程没有await关键字,会持续占用asyncio的单线程事件循环,导致其他异步请求无法被处理。
    而asyncio.sleep(30)是真正的异步挂起操作,会主动释放事件循环,所以这段时间其他请求能正常响应;30秒后执行序列化时,事件循环再次被同步操作占满,就出现了阻塞。

  • 异步ORM的查询阶段并未阻塞:
    异步ORM的数据库查询本身是异步的——当发起查询后,事件循环会挂起当前任务,等待数据库返回结果,这段时间里事件循环可以处理其他请求。真正阻塞的是查询完成后的数据转换/序列化阶段。

解决方案

  1. 将序列化操作移至线程池
    利用asyncio.to_thread把同步的序列化任务丢到线程池执行,释放事件循环:

    import asyncio
    from fastapi import FastAPI
    from tortoise.contrib.pydantic import pydantic_model_creator
    
    ServerPydantic = pydantic_model_creator(Server)
    app = FastAPI()
    
    @app.get("/servers")
    async def get_all_servers():
        queryset = Server.all()
        # 把序列化操作委托给线程池
        serialized_data = await asyncio.to_thread(ServerPydantic.from_queryset, queryset)
        return serialized_data
    
  2. 实现分页查询
    避免一次性返回25000条数据,改为分页接口,每次返回少量数据(比如100条),从根源上降低序列化的CPU负载:

    @app.get("/servers")
    async def get_servers(page: int = 1, page_size: int = 100):
        offset = (page - 1) * page_size
        queryset = Server.all().offset(offset).limit(page_size)
        return await ServerPydantic.from_queryset(queryset)
    
  3. 替换高效序列化库
    使用比Pydantic更快的序列化库(如msgspec)替代默认的Pydantic序列化,大幅降低CPU耗时:

    import msgspec
    
    # 定义msgspec模型
    class ServerMsgspec(msgspec.Struct):
        id: int
        name: str
        # 其他字段...
    
    @app.get("/servers")
    async def get_servers():
        servers = await Server.all().values()  # 直接查询字典格式数据
        return msgspec.json.encode(servers)  # 用msgspec快速序列化
    

内容的提问来源于stack exchange,提问作者Randix Lai Randy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 12:17:17