关于ORM的思考
2013 年 6 月 26 日
ORM,全称是Object-relational mapping,即对象关系映射,借助ORM可以把类和数据库表(schema)对应起来。
我想大家手头上都不会少了ORM这个工具,借助ORM,我们可以以面向对象的方式来写SQL,例如:
我想大家手头上都不会少了ORM这个工具,借助ORM,我们可以以面向对象的方式来写SQL,例如:
session.query(User).where(cls.id == 1).order_by(id.desc()).first()
就相当于
SELECT * FROM user WHERE id == 1 ORDER BY id DESC LIMIT 1
对于SQL生手来说,ORM的方式更容易让人理解。
使用ORM的原因莫过于这么几个:
if...elif...else...
但是ORM也有很大的弊端,那就是:
- 跨数据库对于大多数系统来说都是伪需求,选定一个数据库之后,你真的会换一个数据库吗?
-
跨数据库也没有能够完美的跨数据库,例如SQLAlchemy中,对于
UPDATE/DELETE/INSERT
语句,只有MS SQL Server
支持with_hint
操作,而MySQL/PG/SQLite
都只能使用prefix_with
,但是如果你想要对UPDATE/DELETE
使用数据库索引提示,则是无法实现的,只能
裸写SQL。也就是说,ORM虽然号称跨数据库,其实效果并不好,很多数据库的细节并不能覆盖。 -
生成的SQL不够明确,而且通常比较长。例如上面的查询,ORM会生成
SELECT user.id, user.name, user.age ...
等,把user
的所有属性
都查出来,当然,我们手写的SELECT *
也差不多,这都属于浪费行为,对于一个小型数据库系统,这当然是没有问题,但是数据量一旦达到
亿行的级别,浪费就比较大了。 - 高级一些的SQL或者对性能要求较高的SQL无法准确表述
- 写久了ORM,SQL忘的又快又光,这可以算得上是一个使用ORM的弊端了
所以其实我们需要的是一个能轻松防注入,能自行映射类和数据库表,能轻松写SQL的工具,Python中,SQLAlchemy仍然是首选,Golang中
我倒是认为sqlx能胜任。
一个ORM的错误用法
我经常在ORM中这样写:
class BaseMixin: id = Column(Integer, primary=True) created_at = Column(Datetime, index=True) updated_at = Column(Datetime, index=True) deleted_at = Column(Datetime, index=True)
Go中也是类似的。对于数据量小的系统来说,上面的写法无所谓,但是对于数据量大的系统来说,每一个地方都可能是性能瓶颈。上面的写法
有这么几个缺点:
-
created_at
不应该加索引,对于没有水平分表的系统来说,id是连续自增的(即便分了,也是自增的,不过这个时候就要根据具体场景
来判断是否可以这么做了),因此不需要多加created_at
这个索引 -
deleted_at
的类型不应该是时间,且不应该单独加索引,deleted_at
一般是用来做软删除,记录删除时间是没有必要的,因为有updated_at
,而将其改为Booleanl
类型之后,索引更是没有必要,因为区分度特别低,不是True就是False。正确的方式应该是根据
查询条件,结合前置条件做联合索引。 -
不应该使用
Mixin
或者继承来写基类,不过前两点改造完成之后,倒也的确能够适用于99%的系统。
参考资料: