From 55e0c8549aa6c653c27882bb7266fb4ee770a1db Mon Sep 17 00:00:00 2001 From: Xargin Date: Thu, 11 Jul 2019 01:11:19 +0800 Subject: [PATCH] update --- ch5-web/ch5-05-database.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ch5-web/ch5-05-database.md b/ch5-web/ch5-05-database.md index 95d6f94..3a2df6a 100644 --- a/ch5-web/ch5-05-database.md +++ b/ch5-web/ch5-05-database.md @@ -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只要把你的类或结构体定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如save,create,retrieve,delete。至于ORM在背地里做了什么阴险的勾当,你是不一定清楚的。使用ORM的时候,我们往往比较容易有一种忘记了数据库的直观感受。举个例子,我们有个需求:向用户展示最新的商品列表,我们再假设,商品和商家是1:1的关联关系,我们就很容易写出像下面这样的代码: +完全没有数据库的痕迹,没错,ORM的目的就是屏蔽掉DB层,很多语言的ORM只要把你的类或结构体定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如save,create,retrieve,delete。至于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参数都是更好的选择。