mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 20:52:22 +00:00
golang ->Go & others
fix typo
This commit is contained in:
parent
eb2339a345
commit
3bacf26c45
@ -1,10 +1,10 @@
|
|||||||
# 5.5 Database 和数据库打交道
|
# 5.5 Database 和数据库打交道
|
||||||
|
|
||||||
本节将对 db/sql 官方标准库作一些简单分析,并介绍一些应用比较广泛的开源 orm 和 sql builder。并从企业级应用开发和公司架构的角度来分析哪种技术栈对于现代的企业级应用更为合适。
|
本节将对 db/sql 官方标准库作一些简单分析,并介绍一些应用比较广泛的开源 ORM 和 sql builder。并从企业级应用开发和公司架构的角度来分析哪种技术栈对于现代的企业级应用更为合适。
|
||||||
|
|
||||||
## 从 database/sql 讲起
|
## 从 database/sql 讲起
|
||||||
|
|
||||||
golang 官方提供了 `database/sql` 包来给用户进行和数据库打交道的工作,实际上 `database/sql` 库就只是提供了一套操作 db 的接口和规范,例如抽象好的 sql 预处理(prepare),连接池管理,数据绑定,事务,错误处理等等。官方并没有提供具体某种数据库实现的协议支持。
|
Go官方提供了 `database/sql` 包来给用户进行和数据库打交道的工作,实际上 `database/sql` 库就只是提供了一套操作数据库的接口和规范,例如抽象好的 sql 预处理(prepare),连接池管理,数据绑定,事务,错误处理等等。官方并没有提供具体某种数据库实现的协议支持。
|
||||||
|
|
||||||
和具体的数据库,例如 MySQL 打交道,还需要再引入 MySQL 的驱动,像下面这样:
|
和具体的数据库,例如 MySQL 打交道,还需要再引入 MySQL 的驱动,像下面这样:
|
||||||
|
|
||||||
@ -105,9 +105,9 @@ func main() {
|
|||||||
|
|
||||||
是的,所以社区才会有各种各样的 sql builder 和 orm 百花齐放。
|
是的,所以社区才会有各种各样的 sql builder 和 orm 百花齐放。
|
||||||
|
|
||||||
## 提高生产效率的 orm 和 sql builder
|
## 提高生产效率的 ORM 和 SQL Builder
|
||||||
|
|
||||||
在 web 开发领域常常提到的 orm 是什么?我们先看看万能的维基百科:
|
在 web 开发领域常常提到的 ORM 是什么?我们先看看万能的维基百科:
|
||||||
|
|
||||||
```
|
```
|
||||||
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),
|
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),
|
||||||
@ -115,7 +115,7 @@ func main() {
|
|||||||
从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
|
从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
|
||||||
```
|
```
|
||||||
|
|
||||||
最为常见的 orm 实际上做的是从 db -> 程序的 class / struct 这样的映射。所以你手边的程序可能是从 mysql 的表 -> 你的程序内 class。我们可以先来看看其它的程序语言里的 orm 写起来是怎么样的感觉:
|
最为常见的 ORM 实际上做的是从 db -> 程序的 class / struct 这样的映射。所以你手边的程序可能是从 mysql 的表 -> 你的程序内 class。我们可以先来看看其它的程序语言里的 ORM 写起来是怎么样的感觉:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> from blog.models import Blog
|
>>> from blog.models import Blog
|
||||||
@ -123,7 +123,7 @@ func main() {
|
|||||||
>>> b.save()
|
>>> b.save()
|
||||||
```
|
```
|
||||||
|
|
||||||
完全没有数据库的痕迹,没错。orm 的目的就是屏蔽掉 db 层,实际上很多语言的 orm 只要把你的 class/struct 定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如 save,create,retrieve,delete。至于 orm 背着你背地里做了什么阴险的勾当,你是不一定清楚的。使用 orm 的时候,我们往往比较容易有一种忘记了数据库的直观感受。举个例子,我们有个一需求,是向用户展示最新的商品列表,我们再假设,我们的商品和商家是一对一的关联关系,我们就很容易写出像下面这样的代码:
|
完全没有数据库的痕迹,没错 ORM 的目的就是屏蔽掉DB层,实际上很多语言的 ORM 只要把你的 class/struct 定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如 save,create,retrieve,delete。至于 orm在背地里做了什么阴险的勾当,你是不一定清楚的。使用 ORM 的时候,我们往往比较容易有一种忘记了数据库的直观感受。举个例子,我们有个一个需求:向用户展示最新的商品列表,我们再假设,商品和商家是1:1的关联关系,我们就很容易写出像下面这样的代码:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 伪代码
|
# 伪代码
|
||||||
@ -133,11 +133,11 @@ for product in productList {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
当然了,我们不能批判这样写代码的程序员是偷懒的程序员。因为 orm 一类的工具在出发点上就是屏蔽 sql,让我们对数据库的操作更接近于人类的思维方式。这样很多只接触过 orm 而且又是刚入行的程序员就很容易写出上面这样的代码。
|
当然了,我们不能批判这样写代码的程序员是偷懒的程序员。因为ORM 一类的工具在出发点上就是屏蔽 sql,让我们对数据库的操作更接近于人类的思维方式。这样很多只接触过 orm 而且又是刚入行的程序员就很容易写出上面这样的代码。
|
||||||
|
|
||||||
这样的代码将对数据库的读请求放大了 N 倍。也就是说,如果你的商品列表有 15 个 sku,那么每次用户打开这个页面,至少需要执行 1(查询商品列表) + 15(查询相关的商铺信息) 次查询。这里 N 是 16。如果你的列表页很大,比如说有 600 个条目,那么你就至少要执行 1 + 600 次查询。如果说你的数据库能够承受的最大的简单查询是12w qps,而上述这样的查询正好是你最常用的查询的话,实际上你能对外提供的服务能力是多少呢?是 200 qps!互联网系统的忌讳之一,就是这种无端的读放大。
|
这样的代码将对数据库的读请求放大了 N 倍。也就是说,如果你的商品列表有 15 个 SKU,那么每次用户打开这个页面,至少需要执行 1(查询商品列表) + 15(查询相关的商铺信息) 次查询。这里 N 是 16。如果你的列表页很大,比如说有 600 个条目,那么你就至少要执行 1 + 600 次查询。如果说你的数据库能够承受的最大的简单查询是12w QPS,而上述这样的查询正好是你最常用的查询的话,实际上你能对外提供的服务能力是多少呢?是 200 qps!互联网系统的忌讳之一,就是这种无端的读放大。
|
||||||
|
|
||||||
当然,你也可以说这不是 orm 的问题,如果你手写 sql 你还是可能会写出差不多的程序,那么再来看两个 demo:
|
当然,你也可以说这不是 ORM 的问题,如果你手写 sql 你还是可能会写出差不多的程序,那么再来看两个 demo:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
@ -156,9 +156,9 @@ num, err := o.QueryTable("cardgroup").Filter("Cards__Card__Name", cardName).All(
|
|||||||
|
|
||||||
你可以看得出来这个 Filter 是有表 join 的操作么?当然了,对 beego orm 有过深入使用经验的用户还是会觉得这是在吹毛求疵。但这样的分析想证明的是,orm 想从设计上隐去太多的细节。而方便的代价是其背后的运行完全失控。这样的项目在经过几任维护人员之后,将变得面目全非,难以维护。
|
你可以看得出来这个 Filter 是有表 join 的操作么?当然了,对 beego orm 有过深入使用经验的用户还是会觉得这是在吹毛求疵。但这样的分析想证明的是,orm 想从设计上隐去太多的细节。而方便的代价是其背后的运行完全失控。这样的项目在经过几任维护人员之后,将变得面目全非,难以维护。
|
||||||
|
|
||||||
当然,我们不能否认 orm 的进步意义,orm 的设计初衷是为了让数据的操作和存储的具体实现所剥离。但是上了规模的公司的人们渐渐达成了一个共识,由于隐藏重要的细节,orm 可能是失败的设计。其所隐藏的重要细节对于上了规模的系统开发来说至关重要。
|
当然,我们不能否认 ORM 的进步意义,它的设计初衷就是为了让数据的操作和存储的具体实现所剥离。但是在上了规模的公司的人们渐渐达成了一个共识,由于隐藏重要的细节,ORM 可能是失败的设计。其所隐藏的重要细节对于上了规模的系统开发来说至关重要。
|
||||||
|
|
||||||
相比 orm 来说,sql builer 在 sql 和项目可维护性之间取得了比较好的平衡。首先 sql builer 不像 orm 那样屏蔽了过多的细节,其次从开发的角度来讲,sql builder 简单进行封装后也可以非常高效地完成开发,举个例子:
|
相比 ORM 来说,sql builder 在 sql 和项目可维护性之间取得了比较好的平衡。首先 sql builer 不像 ORM 那样屏蔽了过多的细节,其次从开发的角度来讲,sql builder 简单进行封装后也可以非常高效地完成开发,举个例子:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
where := map[string]interface{} {
|
where := map[string]interface{} {
|
||||||
@ -173,15 +173,15 @@ orders := orderModel.GetList(where, limit, orderBy)
|
|||||||
|
|
||||||
写 sql builder 的相关代码,或者读懂都不费劲。把这些代码脑内转换为 sql 也不会太费劲。所以通过代码就可以对这个查询是否命中数据库索引,是否走了覆盖索引,是否能够用上联合索引进行分析了。
|
写 sql builder 的相关代码,或者读懂都不费劲。把这些代码脑内转换为 sql 也不会太费劲。所以通过代码就可以对这个查询是否命中数据库索引,是否走了覆盖索引,是否能够用上联合索引进行分析了。
|
||||||
|
|
||||||
说白了 sql builder 是 sql 在代码里的一种特殊方言,如果你们没有 dba 并且研发有自己分析和优化 sql 的能力,或者你们公司的 dba 对于学习这样一些 sql 的方言没有异议。那么使用 sql builder 是一个比较好的选择,不会导致什么问题。
|
说白了 sql builder 是 sql 在代码里的一种特殊方言,如果你们没有DBA且研发有自己分析和优化 sql 的能力,或者你们公司的 dba 对于学习这样一些 sql 的方言没有异议。那么使用 sql builder 是一个比较好的选择,不会导致什么问题。
|
||||||
|
|
||||||
另外在一些本来也不需要 dba 介入的场景内,使用 sql builder 也是可以的,例如你要做一套运维系统,且将 mysql 当作了系统中的一个组件,系统的 qps 不高,查询不复杂等等。
|
另外在一些本来也不需要DBA介入的场景内,使用 sql builder 也是可以的,例如你要做一套运维系统,且将 mysql 当作了系统中的一个组件,系统的 QPS 不高,查询不复杂等等。
|
||||||
|
|
||||||
一旦你做的是高并发的 OLTP 在线系统,且想在人员充足分工明确的前提下最大程度控制系统的风险,使用 sql builder 就不合适了。
|
一旦你做的是高并发的 OLTP 在线系统,且想在人员充足分工明确的前提下最大程度控制系统的风险,使用 sql builder 就不合适了。
|
||||||
|
|
||||||
## 脆弱的 db
|
## 脆弱的 db
|
||||||
|
|
||||||
无论是 orm 还是 sql builder 都有一个致命的缺点,就是没有办法进行系统上线的事前 sql 审核。虽然很多 orm 和 sql builder 也提供了运行期打印 sql 的功能,但只在查询的时候才能进行输出。而 sql builder 和 orm 本身提供的功能太过灵活。使得你不可能通过测试枚举出所有可能在线上执行的 sql。例如你可能用 sql builder 写出下面这样的代码:
|
无论是 ORM 还是 sql builder 都有一个致命的缺点,就是没有办法进行系统上线的事前 sql 审核。虽然很多 orm 和 sql builder 也提供了运行期打印 sql 的功能,但只在查询的时候才能进行输出。而 sql builder 和 ORM本身提供的功能太过灵活。使得你不可能通过测试枚举出所有可能在线上执行的 sql。例如你可能用 sql builder 写出下面这样的代码:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
where := map[string]interface{} {
|
where := map[string]interface{} {
|
||||||
@ -204,7 +204,7 @@ res, err := historyModel.GetList(where, limit, orderBy)
|
|||||||
|
|
||||||
从行业分工的角度来讲,现今的互联网公司都有专职的 dba。大多数 dba 并不一定有写代码的能力,去阅读 sql builder 的相关“拼 sql”代码多多少少还是会有一点障碍。从 dba 角度出发,还是希望能够有专门的事前 sql 审核机制,并能让其低成本地获取到系统的所有 sql 内容,而不是去阅读业务研发编写的 sql builder 的相关代码。
|
从行业分工的角度来讲,现今的互联网公司都有专职的 dba。大多数 dba 并不一定有写代码的能力,去阅读 sql builder 的相关“拼 sql”代码多多少少还是会有一点障碍。从 dba 角度出发,还是希望能够有专门的事前 sql 审核机制,并能让其低成本地获取到系统的所有 sql 内容,而不是去阅读业务研发编写的 sql builder 的相关代码。
|
||||||
|
|
||||||
所以现如今,大型的互联网公司核心线上业务都会在代码中把 sql 放在显眼的位置提供给 dba review,以此来控制系统在数据层的风险。结合 golang 举一个例子:
|
所以现如今,大型的互联网公司核心线上业务都会在代码中把 sql 放在显眼的位置提供给 DBA review,以此来控制系统在数据层的风险。结合Go 举一个例子:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const (
|
const (
|
||||||
@ -238,6 +238,6 @@ func GetAllByProductIDsAndCustomerID(ctx context.Context, productIDs []uint64, c
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
像这样的代码,在上线之前把 dao 层的变更集的 const 部分直接拿给 dba 来进行审核,就比较方便了。代码中的 sqlutil.Named 是类似于 sqlx 中的 Named 函数,同时支持 where 表达式中的比较操作符和 in。
|
像这样的代码,在上线之前把DAO层的变更集的 const 部分直接拿给 dba 来进行审核,就比较方便了。代码中的 sqlutil.Named 是类似于 sqlx 中的 Named 函数,同时支持 where 表达式中的比较操作符和 in。
|
||||||
|
|
||||||
这里为了说明简便,函数写得稍微复杂一些,仔细思考一下的话查询的导出函数还可以进一步进行简化。请读者朋友们自行尝试。
|
这里为了说明简便,函数写得稍微复杂一些,仔细思考一下的话查询的导出函数还可以进一步进行简化。请读者朋友们自行尝试。
|
||||||
|
Loading…
x
Reference in New Issue
Block a user