1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-23 20:02:22 +00:00
This commit is contained in:
Xargin 2019-07-11 01:11:19 +08:00
parent 93e06a967a
commit 55e0c8549a

View File

@ -4,7 +4,7 @@
## 5.5.1 从 database/sql 讲起
Go官方提供了`database/sql`包来给用户进行和数据库打交道的工作,实际上`database/sql`库就只是提供了一套操作数据库的接口和规范例如抽象好的SQL预处理prepare连接池管理数据绑定事务错误处理等等。官方并没有提供具体某种数据库实现的协议支持。
Go官方提供了`database/sql`包来给用户进行和数据库打交道的工作,`database/sql`库实际只提供了一套操作数据库的接口和规范例如抽象好的SQL预处理prepare连接池管理数据绑定事务错误处理等等。官方并没有提供具体某种数据库实现的协议支持。
和具体的数据库例如MySQL打交道还需要再引入MySQL的驱动像下面这样
@ -19,7 +19,7 @@ db, err := sql.Open("mysql", "user:password@/dbname")
import _ "github.com/go-sql-driver/mysql"
```
一句import实际上是调用了`mysql`包的`init`函数,做的事情也很简单:
条import语句会调用了`mysql`包的`init`函数,做的事情也很简单:
```go
func init() {
@ -27,7 +27,7 @@ func init() {
}
```
`sql`包的 全局`map`里把`mysql`这个名字的`driver`注册上。实际上`Driver``sql`包中是一个接口:
`sql`包的全局`map`里把`mysql`这个名字的`driver`注册上。`Driver``sql`包中是一个接口:
```go
type Driver interface {
@ -35,7 +35,7 @@ type Driver interface {
}
```
调用`sql.Open()`返回的`db`对象实际上就是这里的`Conn`
调用`sql.Open()`返回的`db`对象就是这里的`Conn`
```go
type Conn interface {
@ -45,7 +45,7 @@ type Conn interface {
}
```
也是一个接口。实际上如果你仔细地查看`database/sql/driver/driver.go`的代码会发现,这个文件里所有的成员全都是接口,对这些类型进行操作,实际上还是会调用具体的`driver`里的方法。
也是一个接口。如果你仔细地查看`database/sql/driver/driver.go`的代码会发现,这个文件里所有的成员全都是接口,对这些类型进行操作,还是会调用具体的`driver`里的方法。
从用户的角度来讲,在使用`database/sql`包的过程中,你能够使用的也就是这些接口里提供的函数。来看一个使用`database/sql``go-sql-driver/mysql`的完整的例子:
@ -114,7 +114,7 @@ func main() {
从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
```
最为常见的ORM实际上做的是从db到程序的类或结构体这样的映射。所以你手边的程序可能是从MySQL的表映射你的程序内的类。我们可以先来看看其它的程序语言里的ORM写起来是怎么样的感觉
最为常见的ORM做的是从db到程序的类或结构体这样的映射。所以你手边的程序可能是从MySQL的表映射你的程序内的类。我们可以先来看看其它的程序语言里的ORM写起来是怎么样的感觉
```python
>>> from blog.models import Blog
@ -122,7 +122,7 @@ func main() {
>>> b.save()
```
完全没有数据库的痕迹没错ORM的目的就是屏蔽掉DB层实际上很多语言的ORM只要把你的类或结构体定义好再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作例如savecreateretrievedelete。至于ORM在背地里做了什么阴险的勾当你是不一定清楚的。使用ORM的时候我们往往比较容易有一种忘记了数据库的直观感受。举个例子我们有个需求向用户展示最新的商品列表我们再假设商品和商家是1:1的关联关系我们就很容易写出像下面这样的代码
完全没有数据库的痕迹,没错ORM的目的就是屏蔽掉DB层很多语言的ORM只要把你的类或结构体定义好再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作例如savecreateretrievedelete。至于ORM在背地里做了什么阴险的勾当你是不一定清楚的。使用ORM的时候我们往往比较容易有一种忘记了数据库的直观感受。举个例子我们有个需求向用户展示最新的商品列表我们再假设商品和商家是1:1的关联关系我们就很容易写出像下面这样的代码
```python
# 伪代码
@ -134,7 +134,7 @@ for product in productList {
当然了我们不能批判这样写代码的程序员是偷懒的程序员。因为ORM一类的工具在出发点上就是屏蔽sql让我们对数据库的操作更接近于人类的思维方式。这样很多只接触过ORM而且又是刚入行的程序员就很容易写出上面这样的代码。
这样的代码将对数据库的读请求放大了N倍。也就是说如果你的商品列表有15个SKU那么每次用户打开这个页面至少需要执行1查询商品列表+ 15查询相关的商铺信息次查询。这里N是16。如果你的列表页很大比如说有600个条目那么你就至少要执行1+600次查询。如果说你的数据库能够承受的最大的简单查询是12万QPS而上述这样的查询正好是你最常用的查询的话实际上你能对外提供的服务能力是多少呢是200 qps互联网系统的忌讳之一就是这种无端的读放大。
这样的代码将对数据库的读请求放大了N倍。也就是说如果你的商品列表有15个SKU那么每次用户打开这个页面至少需要执行1查询商品列表+ 15查询相关的商铺信息次查询。这里N是16。如果你的列表页很大比如说有600个条目那么你就至少要执行1+600次查询。如果说你的数据库能够承受的最大的简单查询是12万QPS而上述这样的查询正好是你最常用的查询的话你能对外提供的服务能力是多少呢是200 qps互联网系统的忌讳之一就是这种无端的读放大。
当然你也可以说这不是ORM的问题如果你手写sql你还是可能会写出差不多的程序那么再来看两个demo
@ -143,7 +143,7 @@ o := orm.NewOrm()
num, err := o.QueryTable("cardgroup").Filter("Cards__Card__Name", cardName).All(&cardgroups)
```
很多ORM都提供了这种Filter类型的查询方式不过实际上在某些ORM背后甚至隐藏了非常难以察觉的细节比如生成的SQL语句会自动`limit 1000`
很多ORM都提供了这种Filter类型的查询方式不过在某些ORM背后可能隐藏了非常难以察觉的细节比如生成的SQL语句会自动`limit 1000`
也许喜欢ORM的读者读到这里会反驳了你是没有认真阅读文档就瞎写。是的尽管这些ORM工具在文档里说明了All查询在不显式地指定Limit的话会自动limit 1000但对于很多没有阅读过文档或者看过ORM源码的人这依然是一个非常难以察觉的“魔鬼”细节。喜欢强类型语言的人一般都不喜欢语言隐式地去做什么事情例如各种语言在赋值操作时进行的隐式类型转换然后又在转换中丢失了精度的勾当一定让你非常的头疼。所以一个程序库背地里做的事情还是越少越好如果一定要做那也一定要在显眼的地方做。比如上面的例子去掉这种默认的自作聪明的行为或者要求用户强制传入limit参数都是更好的选择。