ORM 的优缺点是什么?你如何优化复杂 SQL
很多人一开始接触 ORM(Object Relational Mapping),都会觉得挺爽的嘛,写个类就能自动建表,操作数据库像操作对象一样,省掉了一堆 SQL,简直不要太舒服。比如在 Python 里常用的 SQLAlchemy,写起来大概是这样的:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
classUser(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(50))
age = Column(Integer)
这样你查询一个用户就是:
session.query(User).filter(User.age > 18).all()
根本不用去写 SELECT * FROM users WHERE age > 18。这就是 ORM 的魅力:减少重复劳动、屏蔽 SQL 差异、让开发更快。
但时间久了你就会发现,ORM 不是万能的。比如大家常吐槽的几个点:
性能开销ORM 其实就是在你和数据库之间加了一层翻译。写得越复杂,它帮你生成的 SQL 可能就越绕,数据库执行效率就跟不上。尤其是大批量操作的时候,ORM 往往比直接写 SQL 慢。
N+1 查询问题这个坑特别常见。比如你要查用户和用户的订单,ORM 默认可能是先查出所有用户,再循环一个个去查订单。100 个用户就执行了 101 条 SQL,性能炸裂。
复杂查询难搞ORM 更适合做简单的 CRUD。要是遇到多表 JOIN、窗口函数、复杂的聚合,ORM 写出来的代码不仅难懂,有时候还没法正确转成你想要的 SQL。最后你还是得乖乖写原生 SQL。
学习曲线ORM 框架本身就有很多配置和用法,你要熟练掌握也不轻松。尤其是调试 SQL 的时候,有时候你会想:“直接写 SQL 不香吗?”
那复杂 SQL 怎么优化?
说到底,大多数性能问题都卡在 SQL 上。ORM 用着舒服没问题,但关键地方还是要懂 SQL,才能搞定瓶颈。常见的一些优化思路:
避免 N+1 查询在 SQLAlchemy 里,你可以用
joinedload来一次性把关联数据查出来,减少 SQL 次数。from sqlalchemy.orm import joinedload
users = session.query(User).options(joinedload(User.orders)).all()这样就能生成一条带
JOIN的 SQL,而不是多次查询。索引要建对这个是老生常谈了。比如你有个查询条件经常用到
age,那就给它建个索引。ORM 里也能直接定义:from sqlalchemy import Index
Index("idx_user_age", User.age)索引建对了,查询速度能快一个数量级。
分批操作ORM 默认是把对象一条一条写回数据库,大量数据插入的时候很慢。可以用
bulk_insert_mappings:session.bulk_insert_mappings(User, [
{"name": "Tom", "age": 20},
{"name": "Jerry", "age": 22}
])一次性插入效率高很多。
必要时写原生 SQLORM 框架几乎都支持直接写 SQL。比如 SQLAlchemy:
result = session.execute(
"SELECT name, COUNT(*) FROM orders GROUP BY name HAVING COUNT(*) > 5"
)有些复杂统计用 ORM 拼出来简直是折磨,直接写原生 SQL 既直观又高效。
ORM 适合用在 80% 的常规 CRUD 场景,能帮你节省开发成本。但剩下的 20% ——大批量处理、复杂 SQL、性能优化——还是要靠原生 SQL 和数据库调优。真正写系统时,ORM 和 SQL 混着用才是王道。
就像我经常说的:ORM 是轮子,但别让它变成“方向盘”,该自己掌控的时候还是得上手。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领