一眼识别n+1查询的方法是观察日志中是否反复出现结构高度相似的select * from orders where user_id = ?,且调用次数等于主查询结果数;其根源是orm默认懒加载(lazy='select'),访问user.orders触发新查询。

怎么一眼识别N+1查询?
日志里反复出现结构高度相似的 SELECT * FROM orders WHERE user_id = ?,且调用次数等于主查询结果数,基本就是N+1。这不是SQL写得差,而是ORM默认懒加载在作祟——relationship 默认 lazy='select',访问 user.orders 就触发一次新查询。
实操建议:
- 开启
echo=True或设环境变量SQLALCHEMY_ECHO=1,让SQL日志直接打到控制台 - 别只看Python代码,要抓出真实SQL:构造查询后执行
print(q.statement.compile(compile_kwargs={"literal_binds": True})) - 对生成的SQL跑
EXPLAIN ANALYZE(PostgreSQL)或EXPLAIN FORMAT=JSON(MySQL),重点看是否走索引、有无Seq Scan
joinedload 和 selectinload 怎么选?
两者都解决N+1,但机制和适用场景截然不同,选错反而更慢。
实操建议:
-
joinedload():生成LEFT JOIN,一次查出主表+关联表所有字段。适合关联数据量小、主记录少(如分页查10个用户+每人最多3条订单) -
selectinload():先查主表ID列表,再用WHERE id IN (1,2,3...)批量查关联表。适合关联数据量大、主记录多(如查100个用户,每人平均20条订单),避免JOIN导致的笛卡尔积爆炸 - 别用
subqueryload():已废弃,性能更差,仅用于兼容旧代码 - 注意排序:用
LIMIT时必须配合ORDER BY,否则selectinload的IN查询可能漏数据
为什么加了索引还是慢?
常见误区是只给单列加索引,但复合查询条件(比如 WHERE customer_id = ? AND order_date > ?)需要复合索引,且列顺序直接影响效果。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
实操建议:
- 按查询条件中字段的使用频率和选择性排序:高选择性字段放前面,
WHERE中等值匹配字段优先于范围查询字段 - 在模型中定义复合索引:
__table_args__ = (Index('idx_customer_date', 'customer_id', 'order_date'),) - 避免过度索引:每个INSERT/UPDATE都要更新所有相关索引,写多读少的表要精简索引
-
selectinload依赖orders.user_id有索引,否则IN查询本身就会变慢
批量操作为什么没变快?
用 bulk_insert_mappings 却没提速,大概率是没关事务自动提交或没配连接池。
实操建议:
- 批量操作必须显式控制事务:
session.bulk_insert_mappings(...)后跟session.commit(),别让每条都自动提交 - 连接池参数要调:默认
pool_size=5太小,高并发下会排队,建议设为pool_size=20+max_overflow=30 - 别在循环里新建
Session():每次新建实例都绕过连接池,还浪费session缓存 - 大批量插入慎用ORM对象:直接用
session.execute(insert(User), data_list)比bulk_insert_mappings更底层、更快
真正卡住的地方往往不在ORM语法本身,而在于索引是否覆盖查询路径、事务是否被意外拆散、以及预加载策略是否和实际数据分布匹配。这些点不验证,光改Python代码没用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










