关于ORM的思考
ORM,全称是Object-relational mapping,即对象关系映射,借助ORM可以把类和数据库表(schema)对应起来。
我想大家手头上都不会少了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的原因莫过于这么几个:
- 跨数据库,写一遍ORM查询便可以在多个数据库间切换
- 易于维护,毕竟ORM的查询语句看起来更像是自然语言
- 一般ORM都会自动帮我们做好防注入处理,只要按照ORM规定的方式写,我们就不用自己操心注入的事情
- 可以动态的更新查询条件,即根据不同业务场景
if...elif...else...
追加查询条件,如果用SQL写,就比较麻烦 - 可以以类的方式写数据库,那么也就意味着,可以用继承或者mixin的方式,把所有表中共有的属性,放在基类里
但是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%的系统。
参考资料:
更多文章
本站热门
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- 用peewee代替SQLAlchemy
- frp 源码阅读与分析(一):流程和概念
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(一):runc和containerd
- Docker组件介绍(二):shim, docker-init和docker-proxy
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程