FastAPI × SQLAlchemy 2.0 Async:从“能跑”到“可压测”的完整工程实践

作者:Java私教日期:2025/11/11

一句话总结
SQLAlchemy 2.0 AsyncIO 模式,把 FastAPI 的并发优势兑现成 真正的数据库吞吐;再叠上连接池、事务、迁移、测试四件套,直接上线不踩坑


1. 为什么要“异步 ORM”?

场景同步 SQLAlchemy异步 SQLAlchemy
100 个并发上传开 100 线程 → 100 个连接 → DB 被打爆单线程 20 连接即可跑满 CPU
请求等待 I/O线程上下文切换 8 ms协程切换 0.3 ms
代码风格到处 run_in_threadpool原生 await 一路到底

一句话:同步模式把 FastAPI 的异步事件循环拖回解放前


2. 最小可运行版本(MVP)

安装依赖

1pip install "fastapi[all]" \
2            "sqlalchemy[asyncio]>=2.0" \
3            asyncpg alembic pydantic[email]
4

数据库以 PostgreSQL 为例,MySQL 换成 asyncmy 即可。

项目骨架

1app/
2 ├─ api/
3 │   └─ user.py
4 ├─ core/
5 │   ├─ db.py
6 │   └─ config.py
7 ├─ models/
8 │   └─ user.py
9 ├─ schemas/
10 │   └─ user.py
11 └─ main.py
12

3. 核心代码:Session 生命周期一条龙

app/core/config.py

1from pydantic import BaseSettings
2
3class Settings(BaseSettings):
4    database_url: str = "postgresql+asyncpg://user:pass@localhost:5432/demo"
5    pool_size: int = 20
6    max_overflow: int = 0
7    echo_sql: bool = False
8
9    class Config:
10        env_file = ".env"
11
12settings = Settings()
13

app/core/db.py

1from sqlalchemy.ext.asyncio import (
2    AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
3)
4
5class AsyncDatabaseSession:
6    def __init__(self, url: str, *, pool_size: int = 20, max_overflow: int = 0, echo: bool = False):
7        self.engine: AsyncEngine = create_async_engine(
8            url,
9            pool_size=pool_size,
10            max_overflow=max_overflow,
11            echo=echo,
12            pool_pre_ping=True,          # 心跳保活
13        )
14        self.session_factory = async_sessionmaker(
15            self.engine,
16            expire_on_commit=False,      # 防止懒加载异常
17            class_=AsyncSession,
18        )
19
20    async def close(self):
21        await self.engine.dispose()
22
23db = AsyncDatabaseSession(
24    settings.database_url,
25    pool_size=settings.pool_size,
26    echo=settings.echo_sql,
27)
28

main.py

1from fastapi import FastAPI
2from app.core.db import db
3from app.api import user
4
5app = FastAPI(title="Async SQLAlchemy Demo")
6
7app.include_router(user.router)
8
9@app.on_event("startup")
10async def startup():
11    # 可选:建表
12    # from app.models import Base
13    # async with db.engine.begin() as conn:
14    #     await conn.run_sync(Base.metadata.create_all)
15    pass
16
17@app.on_event("shutdown")
18async def shutdown():
19    await db.close()
20

4. 依赖注入:每次请求一个 Session,自动回滚

app/core/deps.py

1from typing import AsyncGenerator
2from app.core.db import db
3from sqlalchemy.ext.asyncio import AsyncSession
4from fastapi import Depends
5
6async def get_session() -> AsyncGenerator[AsyncSession, None]:
7    async with db.session_factory() as session:
8        try:
9            yield session
10        except Exception:
11            await session.rollback()
12            raise
13        finally:
14            await session.close()
15

yield + rollback 保证请求级事务;抛异常自动回滚,正常则 commit。


5. Model / Schema / CRUD 一条龙

app/models/user.py

1from sqlalchemy import String
2from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
3
4class Base(DeclarativeBase):
5    pass
6
7class User(Base):
8    __tablename__ = "users"
9
10    id: Mapped[int] = mapped_column(primary_key=True, index=True)
11    email: Mapped[str] = mapped_column(String(320), unique=True, index=True)
12    full_name: Mapped[str | None]
13

app/schemas/user.py

1from pydantic import BaseModel, EmailStr
2
3class UserCreate(BaseModel):
4    email: EmailStr
5    full_name: str | None = None
6
7class UserRead(BaseModel):
8    id: int
9    email: EmailStr
10    full_name: str | None
11
12    class Config:
13        orm_mode = True
14

app/api/user.py

1from fastapi import APIRouter, Depends, HTTPException
2from sqlalchemy.ext.asyncio import AsyncSession
3from sqlalchemy import select
4from app.models import User
5from app.schemas import UserCreate, UserRead
6from app.core.deps import get_session
7
8router = APIRouter(prefix="/users", tags=["users"])
9
10@router.post("", response_model=UserRead)
11async def create_user(payload: UserCreate, session: AsyncSession = Depends(get_session)):
12    user = User(**payload.dict())
13    session.add(user)
14    await session.flush()          # 获取 id
15    await session.commit()
16    await session.refresh(user)
17    return user
18
19@router.get("/{uid}", response_model=UserRead)
20async def read_user(uid: int, session: AsyncSession = Depends(get_session)):
21    user = await session.get(User, uid)
22    if not user:
23        raise HTTPException(404, "User not found")
24    return user
25

6. 迁移:Alembic 同样能异步

初始化

1alembic init -t async migrations
2

修改 alembic.ini 中的 sqlalchemy.urlpostgresql+asyncpg://...

migrations/env.py

1from app.core.config import settings
2from app.models import Base
3target_metadata = Base.metadata
4
5def do_run_migrations(connection):
6    context.configure(connection=connection, target_metadata=target_metadata)
7    with context.begin_transaction():
8        context.run_migrations()
9
10async def run_async_migrations():
11    from sqlalchemy.ext.asyncio import AsyncEngine
12    connectable = AsyncEngine(create_async_engine(settings.database_url))
13    async with connectable.connect() as connection:
14        await connection.run_sync(do_run_migrations)
15    await connectable.dispose()
16

生成 / 升级

1alembic revision --autogenerate -m "init"
2alembic upgrade head
3

7. 测试:pytest-asyncio + 异步数据库事务

tests/conftest.py

1import pytest
2from httpx import AsyncClient
3from app.main import app
4from app.core.db import db as db_instance
5from sqlalchemy.pool import StaticPool
6from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
7
8@pytest.fixture(scope="session")
9async def engine() -> AsyncEngine:
10    # 内存 SQLite 也可以异步,但 PostgreSQL 更真实
11    engine = create_async_engine(
12        "postgresql+asyncpg://test:test@localhost:5432/test",
13        poolclass=StaticPool,
14    )
15    yield engine
16    await engine.dispose()
17
18@pytest.fixture
19async def session(engine: AsyncEngine):
20    conn = await engine.begin()
21    sess = db_instance.session_factory(bind=conn)
22    yield sess
23    await sess.close()
24    await conn.rollback()
25    await conn.close()
26
27@pytest.fixture
28async def client() -> AsyncGenerator[AsyncClient, None]:
29    async with AsyncClient(app=app, base_url="http://test") as c:
30        yield c
31

tests/test_user.py

1import pytest
2from sqlalchemy import select
3from app.models import User
4
5@pytest.mark.asyncio
6async def test_create_user(client, session):
7    res = await client.post("/users", json={"email": "[email protected]", "full_name": "abc"})
8    assert res.status_code == 201
9    data = res.json()
10    assert data["email"] == "[email protected]"
11
12    user = await session.get(User, data["id"])
13    assert user is not None
14

8. 性能调优 checklist

参数建议值说明
pool_sizeCPU 核心 × 220 并发已能压到 10k RPS
max_overflow0防止突发连接打爆 DB
pool_pre_ping=True必须网络闪断后自动重连
expire_on_commit=False必须否则 commit 后属性失效
echo=False生产关闭减少序列化开销

9. 常见错误速查表

异常原因解法
greenlet_spawn has not been called用了同步引擎create_async_engine
DetachedInstanceError会话关闭后访问属性expire_on_commit=False + await session.refresh()
InterfaceError: connection already closed协程间复用 Session一个请求一个 Session,禁止全局单例
ImportError: asyncmyMySQL 驱动未装pip install asyncmy

10. 结语

FastAPI 的异步生态里,数据库是最后一道闸门
用上 SQLAlchemy 2.0 AsyncIO 之后,I/O 等待不再是瓶颈,压测曲线直接多一个量级。
把本文的 db.py + deps.py 复制走,10 分钟就能让老项目原地起飞。Happy async coding!


FastAPI × SQLAlchemy 2.0 Async:从“能跑”到“可压测”的完整工程实践》 是转载文章,点击查看原文


相关推荐


删一个却少俩:Antd Tag 多节点同时消失的原因
顺凡2025/11/9

删一个却少俩:Antd Tag 多节点同时消失的原因 需求 一个表单的小需求,能填写多个福利,最多十个,福利名称允许重复,和官方的动态添加和删除示例交互一模一样,只是官方示例不支持 tag 内容重复,使用的 tag 内容作为 key 我复制丢给 AI,下掉去重,限制个数,好!满足需求了,key 值怎么办不能用重复的,拼个索引吧,最后主要代码如下, 反问一下:你觉得这会有什么问题,能达到删一个少俩的效果吗🤔??? 问题 大家应该都知道用 index 作为 key,会有一些问题,对于我这个需


程序员副业 | 2025年10月复盘
嘟嘟MD2025/11/7

本文首发于公众号:嘟爷创业日记 。 我已经坚持日更600天+,欢迎过来追剧~ 大家好,我是嘟嘟MD,一个10年程序员,现在离职创业,有700天了,我每个月都会写一篇总结复盘,让大家可以近距离看看一个离职程序员都在干什么,今天这篇是九月份的总结,大概2000字,略长,有空的可以翻翻,希望对大家有一丢丢的借鉴作用! 一、月度大事 10月结束了,一直拖到现在才有空汇总下10月份的进度,整体来说对外的合作少了,组织内的事情多了。 1:公众号运营+B站视频运营 公众号和B站视频运营还是我的最高优先级


在 Vue3 项目中使用 el-tree
代码工人笔记2025/11/3

在 Vue3 项目中使用 el-tree 文章目录 一、基础用法1. 引入组件 二、常用功能与配置1. 节点选择(复选框 / 单选)2. 展开 / 折叠控制3. 自定义节点内容4. 搜索过滤节点5. 获取选中节点 三、动态加载节点 一、基础用法 1. 引入组件 <template> <el-tree :data="treeData" :props="defaultProps" @node-click="handleNode


SQL之表的查改(上)
啊吧怪不啊吧2025/10/31

目录 1. Retrieve 1.2 Select 1.2.1全列插入 1.2.2 use 1.2.3 指定列查询 1.2.4 select+固定值 1.2.5 列值修改查询 1.2.6 别名 1.2.7 查询结果去重 在前面的文章中我们链接了表的增删操作,今天我们来聊一下表的查找与修改操作。 1. Retrieve 首先我们要了解到Retrieve不是某一个具体的指令,它描述的是 “从数据库表中获取数据” 这一行为本身,而实现这种行为的具体技


Swift 并发编程新选择:Mutex 保护可变状态实战解析
unravel20252025/10/29

前言 Swift 5.5 带来 async/await 与 Actor 后,「用 Actor 包一层」几乎成了默认答案。 但在日常开发里,我们经常会遇到两种尴尬: 只想保护一个计数器、缓存或 token,却不得不把整段逻辑都改成异步; 把对象放到 @MainActor 后,发现后台线程也要用,结果到处是 await。 Apple 在 Swift 5.9 前后把 Mutex 正式搬进标准库(通过 Synchronization 模块),给“同步但不想异步”的场景提供了第三条路。 Mutex 是


F032 材料科学文献知识图谱可视化分析系统(四种知识图谱可视化布局) | vue + flask + echarts + d3.js 实现
B站麦麦大数据2025/10/26

文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站,有好处! 编号: F032 视频 neo4j 文献知识图谱可视化分析系统 | vue + flask + echarts + d3.js 实现 1 系统简介 系统简介:本系统是一个基于Vue+Flask构建的材料科学文献知识图谱可视化分析系统,其核心功能围绕文献数据的抓取、分析、可视化和用户管理展开。主要包括:主页模块,用于展示最新文献卡片,方便用户快速了解最新动态;文献搜索功能,支持用户通过关键词或其他条


【DeepSeek新开源】DeepSeek-OCR如何用“视觉压缩”革新长文本处理
kakaZhui2025/10/23

最近DeepSeek团队刚放出DeepSeek-OCR项目,不再将其视为一个简单的OCR(光学字符识别)工具,而是将其作为一个开创性的实验平台,旨在探索和验证一个激进的理念:我们能否利用视觉模态作为一种超高效的文本信息压缩媒介? 即,将长篇的数字文本“渲染”成一张图像,再用一个强大的视觉语言模型(VLM)从这张图像中“读”出原文。 接下来我们一起看下DeepSeek-OCR从“视觉压缩”的核心哲学,到其创新的DeepEncoder架构和多分辨率支持,再到其庞大的数据工程和训练管线。 1. 引


想偷卷?但微信不支持md文档?这个软件助你!
前端AC2025/10/22

📝 Markdown 查看器 - 现代化的文档预览工具 一个基于 React 19 + TypeScript 构建的现代化 Markdown 文档查看器,支持实时预览、语法高亮、数学公式渲染等功能。 在微信或浏览器上打开此编辑器,上传你的md文档可以上课偷偷看自己写的博客哈哈,这个是我解决微信这个没有md预览的痛点,自己用ai搞了一个小工具出来,效果还不错,还有图片可以借助图床工具:图床 - 简单、快速、免费的图床把自己图片上传到这里,就不会导致路径问题了。 项目概述 项目背景 在日常开发


告别页面呆板!这5个DOM操作技巧让你的网站活起来
良山有风来2025/10/21

你是不是经常遇到这样的情况:精心设计的页面看起来很美,但用户操作起来却毫无反应?点击按钮没反馈,表单提交没提示,页面切换生硬得像在翻纸质书? 这就像给用户端上了一盘色香味俱全的菜,结果吃起来却发现是冷的。问题就出在——你还没有掌握DOM操作的真正精髓。 今天,我就带你彻底搞懂JavaScript DOM操作,从基础到实战,让你的网页真正“活”起来。读完这篇文章,你不仅能理解DOM的工作原理,还能掌握5个让用户体验飙升的实用技巧。 什么是DOM?它为什么如此重要? 简单来说,DOM就是连接HTML


JVM 调优黄金三步法:监控→分析→验证
老K的Java兵器库2025/10/19

JVM 调优黄金三步法:监控→分析→验证 (方法论 + 案例 + 压测验证,新手也能照抄) 关键词:JVM 调优、监控、分析、验证、压测、方法论、黄金三步 阅读时长:20 min 环境:CentOS 7 + OpenJDK 8u342 + SpringBoot 1.5 + JMeter 5 适合:1~5 年 Java 开发、生产调优无思路、面试「JVM 怎么调优」标准答案 一、0 基础速记:黄金三步一句话 步骤目标一句话监控发现瓶颈先知道「哪里慢」再动手分析定位根因用数据证

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0