惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
罗磊的独立博客
人人都是产品经理
人人都是产品经理
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
L
Lohrmann on Cybersecurity
博客园 - 【当耐特】
量子位
Last Week in AI
Last Week in AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
Cyber Attacks, Cyber Crime and Cyber Security
腾讯CDC
有赞技术团队
有赞技术团队
Cyberwarzone
Cyberwarzone
T
Tor Project blog
V
V2EX
L
LINUX DO - 热门话题
Security Latest
Security Latest
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
NISL@THU
NISL@THU
C
Cisco Blogs
T
Tailwind CSS Blog
G
GRAHAM CLULEY
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
小众软件
小众软件
K
Kaspersky official blog
博客园 - 司徒正美
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
S
Schneier on Security
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
The Exploit Database - CXSecurity.com
Scott Helme
Scott Helme
J
Java Code Geeks
博客园 - 聂微东
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
C
Cybersecurity and Infrastructure Security Agency CISA
F
Fortinet All Blogs
T
Threat Research - Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
雷峰网
雷峰网

博客园 - 一名程序媛呀

Anki插件开发必知必会:钩子函数与右键菜单定制 httpx 传参总报错?这次把 GET、POST、文件上传到响应处理的坑给你一次填平 从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 还在 XHR、Fetch 和 Axios 之间纠结?我踩过的坑,希望你一个都不用碰到 你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南 Uvicorn、Gunicorn 傻傻分不清?FastAPI 生产部署避坑指南 Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招! 刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API 别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了 掏出手机就能搭个 WebDAV 同步服务器?这操作有点香 别只盯着GitBook了!这个文档神器让你的笔记秒变网站 写爬虫时用了代理还被封?Python 代理的那些隐藏坑,我替你踩明白了 FastAPI 身份验证总踩坑?这份 FastAPI Users “避坑指南”请收好 旧手机别扔!用 Termux 搭个私人云盘,比网盘香多了 你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透 写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 聊聊 fetch 使用中我踩过的那些坑和正确打开方式 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 告别 Typora 后的新欢:我把所有笔记迁移到了 Obsidian 这个“第二大脑” 你的Agent API还在裸奔?从认证到沙箱,我用FastAPI搭了几道防线 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案 让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践 FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“ 初探:用 FastAPI 搭建你的第一个 AI Agent 接口 FastAPI 少有人提的实用技巧:把 Depends 依赖提到路由层,代码少写60% FastAPI 生产环境静态文件完全指南:从 /favicon.ico 404 到 HSTS 混合内容,一次全根治 用了loguru我才明白,Python日志还能这么写 FastAPI 后台任务:BackgroundTasks 的使用场景与注意事项 FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚 FastAPI 文件上传避坑全指南:分块存盘、类型校验与安全兜底 FastAPI + Pydantic 模型终极实战手册:从能跑就行到固若金汤,这些技巧你一定用得上 FastAPI + SQLAlchemy 2.0 通用CRUD操作手册 —— 从同步到异步,一次讲透 FastAPI订单防超卖实战:从数据库锁到Saga分布式事务,这一篇给你理清了 FastAPI 生产环境避坑指南:用 Alembic 管理数据库迁移,别再手动改表结构了! FastAPI服务半夜又挂了?先别急着重启,查查你的数据库连接池“池子”是不是漏了 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 Vue 3 组合式 API 香是香,但从Vue2迁移时你可别像我当初一样踩进这 3 个深坑里 我用fastapi-scaff搭了个项目,两天工期缩到两小时,老板以为我开挂了 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! FastAPI自动生成的API文档太丑?我花了一晚上把它改成了客户愿意付费的样子 告别手写 API 胶水代码:FastAPI 与 Vue 的“契约自动机” OpenAPI 实战 FastAPI + Vue 前后端分离实战:我的项目结构“避坑指南” FastAPI + Celery 实战:异步任务里调用 Redis 和数据库的全解析,及生产级组织方案 FastAPI里玩转Redis和数据库的正确姿势,别让异步任务把你坑哭了! FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍 FastAPI子应用挂载:别再让root_path坑你一夜 FastAPI项目半夜报警吵醒你?聊聊告警这事儿怎么搞! 别再数据线了!用FastAPI 5分钟搭个局域网文件+剪贴板神器 FastAPI单元测试实战:别等上线被喷才后悔,TestClient用对了真香! FastAPI状态共享秘籍:别再让中间件、依赖和路由“各自为政”了! FastAPI实战:WebSocket vs Socket.IO,这回真给我整明白了! FastAPI + PostgreSQL 实战:给应用装上“缓存”和“日志”翅膀 FastAPI + PostgreSQL 实战:从入门到不踩坑,一次讲透
从0到1,FastAPI + PostgreSQL + Tortoise ORM 实战避坑指南
一名程序媛呀 · 2026-03-12 · via 博客园 - 一名程序媛呀

你是不是也经历过这种纠结:想用 FastAPI 写个带数据库的项目,却在 SQLAlchemy 和 Tortoise ORM 之间反复横跳?

欢迎新老朋友👋!作为一名在代码堆里摸爬滚打多年的全栈程序媛,今天咱们就聊聊 FastAPI + PostgreSQL + Tortoise ORM 这套组合拳。我会把我自己踩过的坑、修复的数据迁移事故,全都摊开来跟你讲。这不是官方文档的复述,而是一份可以直接拿来用的「避坑实战笔记」。

🎯 本文能帮你解决什么?

✅ 快速搭好 FastAPI + PostgreSQL 的项目骨架

✅ 搞懂 Tortoise ORM 的模型定义和关系用法(附代码片段)

✅ 用 Aerich 优雅地管理数据库迁移,不再手动改表

✅ 整合 Jinja2 模板,让 ORM 查询结果直接渲染到前端

✅ 总结 5 个最容易翻车的坑,附解决方案

📌 主要内容脉络

🔸 为什么要选 Tortoise ORM?——异步世界里的「翻译官」

🔸 环境搭建与配置——别在第一步就摔跤

🔸 模型定义与关系——像搭积木一样建表

- 字段类型避坑指南

- 一对多、多对多实战

🔸 数据迁移 Aerich——数据库的「版本控制」

- 初始化、变更、回滚全流程

🔸 模板渲染——把数据变成页面

🔸 常见问题 & 急救包

⚙️ 第一部分:为什么是 Tortoise ORM?

你可能会问:FastAPI 官方文档里推荐用 SQLAlchemy 啊,为什么偏要用 Tortoise

说实话,复杂大型项目还是老老实实配 SQLAlchemy + 异步驱动,它毕竟经过了时间的沉淀,够稳。但对于新手新项目或快速原型来说,就有点像穿着皮鞋跑步——能跑,但别扭。直到我发现了 Tortoise ORM,它简直就是为异步 Python 而生的。你可以把它想象成一个「实时翻译官」,你写 Python 对象,它自动翻译成 SQL,而且全程异步非阻塞,跟 FastAPI 的 async/await 天生一对。

💡 核心优势:类 Django ORM 的语法(上手快)、全异步支持、自带分页和信号,最关键的是——配合 Aerich 做迁移,比 Alembic 在异步环境下的配置简单太多了!

🔧 第二部分:搭建项目骨架(含配置代码)

好,咱们先来搭环境。假设你已经有了 Python 3.8+ 和 PostgreSQL 实例。

# 安装依赖
pip install fastapi uvicorn[standard] tortoise-orm[asyncpg] aerich asyncpg tomlkit jinja2

这里提醒一句:如果你偶尔要跑一些同步脚本,或者用一些依赖 psycopg2 的工具(比如某些数据库管理 GUI),那装个 psycopg2-binary 也无妨。记得用 binary 版本,别给自己找编译的麻烦 😉,千万别学我当初偷懒,直接用 psycopg2 而不是 psycopg2-binary,结果部署到 Linux 上编译报错……用 binary 版本省心很多。

📁 项目结构建议

my_project/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── models.py # Tortoise 模型定义
│ ├── schemas.py # Pydantic 模型(可选)
│ ├── routers/ # 路由
│ └── templates/ # Jinja2 模板
├── migrations/ # Aerich 迁移目录(自动生成)
├── aerich.ini # Aerich 配置
└── tortoise_config.py # 数据库配置

⚡ 配置 Tortoise ORM(tortoise_config.py)

TORTOISE_ORM = {
    'connections': {
        'default': {
            'engine': 'tortoise.backends.asyncpg',  # PostgreSQL 异步驱动
            'credentials': {
                'host': 'localhost',
                'port': '5432',
                'user': 'postgres',
                'password': 'yourpassword',
                'database': 'fastapi_db',
            }
        }
    },
    'apps': {
        'models': {
            'models': ['app.models', 'aerich.models'],  # 必加 aerich.models
            'default_connection': 'default',
        }
    }
}

🧱 第三部分:定义模型(带着感情写代码)

咱们写一个简单的博客系统的模型:用户、文章、标签。看 Tortoise 怎么用 Python 类描述表关系。

# app/models.py
from tortoise import Model, fields

class User(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=100)
    email = fields.CharField(max_length=200, unique=True)
    created_at = fields.DatetimeField(auto_now_add=True)

class Meta:
    table = "users"

class Article(Model):
    id = fields.IntField(pk=True)
    title = fields.CharField(max_length=200)
    content = fields.TextField()
    author = fields.ForeignKeyField('models.User', related_name='articles')
    tags = fields.ManyToManyField('models.Tag', related_name='articles', through='article_tag')
    created_at = fields.DatetimeField(auto_now_add=True)

class Tag(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=50, unique=True)

看到没有?ForeignKeyField 和 ManyToManyField 的写法几乎和 Django 一样。但有个坑:related_name 必须指定,否则查询时你会摸不着头脑。还有,多对多的 through 表可以自动生成,但如果你想自定义中间表,也可以单独定义模型。

🚚 第四部分:数据迁移 Aerich(装修不改图纸)

模型定义好了,怎么应用到数据库?这就是 Aerich 出场的时候了。它就像装修时的图纸版本管理,每次改模型就生成一份迁移文件。

初始化 Aerich(只在项目开始时做一次)

aerich init -t tortoise_config.TORTOISE_ORM
aerich init-db

执行完后,项目里会生成 migrations 文件夹和 aerich.ini 文件。注意:TORTOISE_ORM 配置里的 'models' 必须包含 'aerich.models',否则 init-db 会报错说找不到 aerich 表。

每次修改模型后

aerich migrate --name add_user_bio # 生成迁移文件
aerich upgrade # 应用迁移到数据库

这里分享一个我踩过的坑:如果你修改了字段名,Aerich 不会自动识别字段重命名,而是先 drop 原字段再 add 新字段,导致数据丢失!所以改字段名时,最好手动编辑迁移文件,用 rename 操作。

🖥️ 第五部分:在 FastAPI 中使用 ORM(附 CRUD 示例)

在 main.py 里初始化 Tortoise,并写几个接口试试。

# app/main.py
from fastapi import FastAPI, Request
from tortoise.contrib.fastapi import register_tortoise
from app import models  # 导入模型
from tortoise_config import TORTOISE_ORM

app = FastAPI()

register_tortoise(
    app,
    config=TORTOISE_ORM,
    generate_schemas=False,  # 我们使用 aerich 管理,所以关掉自动生成
    add_exception_handlers=True,
)

@app.get("/users")
async def get_users():
    users = await models.User.all().values()
    return {"users": users}

@app.post("/users")
async def create_user(name: str, email: str):
    user = await models.User.create(name=name, email=email)
    return {"id": user.id}

看,查询直接用 await,一点阻塞都没有。而且 .values() 可以直接转成字典,省去了序列化的麻烦。

🎨 第六部分:模板渲染(让数据见人)

如果你想做一个带后端的网站,而不是纯 API,可以集成 Jinja2。把数据库里查出来的用户列表渲染到 HTML 上。

# main.py 添加
from fastapi.templating import Jinja2Templates

templates = Jinja2Templates(directory="app/templates")

@app.get("/users-page")
async def users_page(request: Request):
    users = await models.User.all()
    return templates.TemplateResponse("users.html", {"request": request, "users": users})

templates/users.html 里,直接用 Tortoise 返回的模型对象,可以像 {{ user.name }} 这样访问。但注意:模板里不能使用 await,所以如果你在查询时没有预取关联字段,模板里访问关联对象会报错。解决方案:要么在视图中用 .prefetch_related(),要么在模板中使用 {% for article in user.articles %} 时确保已经加载。

🚨 第七部分:常见问题 & 急救包

问题1: 执行 aerich migrate 提示 “No changes detected
解决: 检查模型是否在 TORTOISE_ORM apps.models.models 列表中正确引入,且模型有变化(包括 Meta 类中的 table 名称修改也算)。

问题2: 数据库连接数过多,导致 “too many clients
解决: Tortoise 默认连接池大小为 20,可以在 credentials 里设置 'max_connections': 10 限制,并确保每次请求后释放连接——其实 register_tortoise 已经帮我们管理好了生命周期,通常不用手动关。

问题3: 事务操作失败不回滚
解决: 使用 @atomic() 装饰器或 async with in_transaction() 确保原子性。记住,Tortoise 的事务是基于连接上下文的,别在事务里切换连接。

问题4: 多对多关系查询重复数据
解决: 使用 .distinct() 或者通过中间表手动查询。

问题5: 迁移时字段类型变更导致数据截断
解决: 生产环境操作前先备份,或者编写数据迁移脚本。Aerich 不支持自动数据迁移,需要手动编辑迁移文件中的 SQL。

💬 最后啰嗦一句

Tortoise ORM 真的让我在 FastAPI 项目中找回了 Django 那种「浑然一体」的感觉。但工具再好,也得多写多试。希望这篇实战笔记能帮你绕过我当年踩过的坑,早点下班!

如果你在项目中遇到了其他奇葩问题,欢迎评论区留言,咱们一起吐槽一起解决~

🎁 老朋友的经验,不点赞收藏可就亏大了