tortoise-orm虽原生异步,但sqlalchemy 2.0+在复杂查询、join、事务及连接池管理上更成熟稳定;压测需用asyncpg、关闭预热、含join与returning操作,关注p95延迟而非平均qps。

直接测 QPS,别信文档说的“原生异步就更快”
很多人看到 Tortoise-ORM 宣称“原生 asyncio”,SQLAlchemy 要靠 AsyncSession + create_async_engine 拼装,就默认 Tortoise 更快。实际压测结果常相反——尤其在复杂查询、多表 join 或事务嵌套场景下。SQLAlchemy 2.0+ 的查询编译器更成熟,生成的 SQL 更贴近手写,而 Tortoise 的链式 DSL 在深层嵌套时容易生成冗余子查询或漏 GROUP BY。
实操建议:
- 用
asyncpg驱动测 PostgreSQL(别用aiomysql,它底层有 GIL 争用,会掩盖 ORM 差异) - 统一关闭连接池预热(
min_size=1),避免缓存干扰真实 I/O 延迟 - 压测接口必须含至少一次
SELECT ... JOIN和一次INSERT ... RETURNING,单条.filter().first()测不出差异 - 用
locust或artillery发 200+ 并发,持续 60 秒,看 P95 延迟和错误率,不是只看平均 QPS
查慢查询日志,Tortoise 的 SQL 生成逻辑常被忽略
Tortoise 默认把 await User.all().annotate(count=Count('id')) 翻译成 SELECT *, COUNT(id) FROM users,没加 GROUP BY —— PostgreSQL 直接报错。你得手动补 .group_by('id')。而 SQLAlchemy 的 select(func.count()).join(...).group_by(...) 是显式构造,出错前就能 type check。
常见坑点:
- Tortoise 的
fields.ForeignKeyField('models.User', 'user')字符串引用,在 PyCharm 里无法跳转,重构时字段名改了但迁移脚本不报错,上线后KeyError - SQLAlchemy 的
selectinload()能精准控制 N+1,Tortoise 的.prefetch_related()在深度嵌套时可能触发多次 round-trip - 两者都支持原生 SQL,但 Tortoise 的
await execute_query('...')返回的是 raw tuple,SQLAlchemy 的result.mappings().all()自动转 dict,开发效率差一截
连接池配置不对,再好的 ORM 也白搭
异步性能瓶颈往往不在 ORM 层,而在连接池。Tortoise 默认用 max_connections=20,SQLAlchemy 默认是 pool_size=5(AsyncEngine 的 pool_pre_ping=False)。高并发下前者更容易打满连接,后者反而因连接复用率低导致频繁建连。
关键参数对比:
- Tortoise:调
connections.default.max_pool_size,但要注意它不区分读写,所有操作共用同一池 - SQLAlchemy:用
create_async_engine(..., pool_size=10, max_overflow=20, pool_pre_ping=True),pool_pre_ping能自动剔除失效连接,Tortoise 没这功能 - 两者都要配
min_size预热,否则首波请求延迟飙升——Tortoise 的aerich upgrade不触发连接池初始化,SQLAlchemy 的engine.connect()可主动预热
迁移工具可靠性决定长期维护成本
选型时最容易被忽略的是迁移稳定性。Tortoise 的 aerich 依赖运行时模型 diff,tortoise-cli aerich migrate 生成的文件不含 DDL SQL,只存 Python 操作指令。一旦模型定义有歧义(比如两个字段同时设 unique=True),它可能生成非法 SQL;而 SQLAlchemy 配合 Alembic,每次 alembic revision --autogenerate 都输出可 review 的 SQL 文件,支持手工修正。
真实踩坑场景:
- Tortoise 迁移失败后 rollback 不干净,残留临时表,需手动
DROP TABLE - SQLAlchemy 的
op.alter_column()支持跨数据库语法适配(PostgreSQL 的ALTER COLUMN TYPEvs MySQL 的MODIFY COLUMN),Tortoise 的fields.TextField(null=True)改成not_null=True在不同库行为不一致 - 团队协作时,Tortoise 的迁移文件没法 git blame 到具体字段变更人,Alembic 的 revision id 可追溯
真正影响性能的,往往是连接池配置、SQL 生成质量、迁移可维护性这些“看不见”的环节。ORM 层的语法糖只是表象,别让链式调用的流畅感掩盖了底层 SQL 的失控风险。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











