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的数据库查询本身是异步的——当发起查询后,事件循环会挂起当前任务,等待数据库返回结果,这段时间里事件循环可以处理其他请求。真正阻塞的是查询完成后的数据转换/序列化阶段。
解决方案
将序列化操作移至线程池
利用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实现分页查询
避免一次性返回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)替换高效序列化库
使用比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
相关产品推荐
相关产品推荐

