From 8f2ad484d5105864cdaceaef39dc9059c0df46ed Mon Sep 17 00:00:00 2001
From: meilihao <563278383@qq.com>
Date: Sat, 16 Sep 2017 13:47:29 +0800
Subject: [PATCH 01/11] =?UTF-8?q?r=20=E4=BD=BF=E7=94=A8docsify=E7=AE=A1?=
=?UTF-8?q?=E7=90=86md=E6=96=87=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
使用docsify避免生成的一堆 .html 文件"污染" commit 记录
---
.nojekyll | 0
README.md | 7 ++
_sidebar.md | 16 +++++
index.html | 46 +++++++++++++
tutorial/accessing.md | 38 +++++++++++
tutorial/connection-pool.md | 12 ++++
tutorial/errors.md | 125 ++++++++++++++++++++++++++++++++++++
tutorial/importing.md | 21 ++++++
tutorial/index.md | 8 +++
tutorial/modifying.md | 66 +++++++++++++++++++
tutorial/nulls.md | 43 +++++++++++++
tutorial/overview.md | 15 +++++
tutorial/prepared.md | 77 ++++++++++++++++++++++
tutorial/references.md | 13 ++++
tutorial/retrieving.md | 123 +++++++++++++++++++++++++++++++++++
tutorial/surprises.md | 88 +++++++++++++++++++++++++
tutorial/varcols.md | 46 +++++++++++++
17 files changed, 744 insertions(+)
create mode 100644 .nojekyll
create mode 100644 README.md
create mode 100644 _sidebar.md
create mode 100644 index.html
create mode 100755 tutorial/accessing.md
create mode 100755 tutorial/connection-pool.md
create mode 100755 tutorial/errors.md
create mode 100755 tutorial/importing.md
create mode 100755 tutorial/index.md
create mode 100755 tutorial/modifying.md
create mode 100755 tutorial/nulls.md
create mode 100755 tutorial/overview.md
create mode 100755 tutorial/prepared.md
create mode 100755 tutorial/references.md
create mode 100755 tutorial/retrieving.md
create mode 100755 tutorial/surprises.md
create mode 100755 tutorial/varcols.md
diff --git a/.nojekyll b/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fc13e3c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# Go database/sql 指南
+
+- [原文](http://go-database-sql.org)
+- [git-origin#1ec3d97 on 17 Mar 2016](https://github.com/VividCortex/go-database-sql-tutorial)
+
+## lisence
+[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
diff --git a/_sidebar.md b/_sidebar.md
new file mode 100644
index 0000000..77090d4
--- /dev/null
+++ b/_sidebar.md
@@ -0,0 +1,16 @@
+- 入门
+ - [介绍](tutorial/index.md)
+ - [概述](tutorial/overview.md)
+- 使用
+ - [导入数据库驱动](tutorial/importing.md)
+ - [访问数据库](tutorial/accessing.md)
+ - [获取结果集](tutorial/retrieving.md)
+ - [修改数据和使用事务](tutorial/modifying.md)
+ - [使用预编译语句](tutorial/prepared.md)
+ - [错误处理](tutorial/errors.md)
+ - [使用Null](tutorial/nulls.md)
+ - [使用未知列](tutorial/varcols.md)
+ - [连接池](tutorial/connection-pool.md)
+ - [意外,反模式,限制](tutorial/surprises.md)
+- 其他
+ - [相关阅读和资源](tutorial/references)
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d315679
--- /dev/null
+++ b/index.html
@@ -0,0 +1,46 @@
+
+
+
+
+ Go database/sql 指南
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tutorial/accessing.md b/tutorial/accessing.md
new file mode 100755
index 0000000..e6fb432
--- /dev/null
+++ b/tutorial/accessing.md
@@ -0,0 +1,38 @@
+# 访问数据库
+
+现在,你已经导入了驱动package,那么你就可以创建一个数据库对象了,即`sql.DB`.
+
+为了创建`sql.DB`,你可使用`sql.Open()`,它返回一个`*sql.DB`.
+
+```go
+func main() {
+ db, err := sql.Open("mysql",
+ "user:password@tcp(127.0.0.1:3306)/hello")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer db.Close()
+}
+```
+
+下面是示例的一些说明:
+
+1. `sql.Open`的第一个参数是驱动的名称.这是驱动注册自己到`database/sql`时所使用的字符串.为了避免混淆,通常是与驱动的包名相同.例如,`mysql`用于[github.com/go-sql-driver/mysql](github.com/go-sql-driver/mysql).但也有一些驱动名称不遵循约定(使用数据库名称),比如,`sqlite3`用于[github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3), `postgres`用于[github.com/lib/pq](https://github.com/lib/pq).
+2. 第二个参数是驱动的特定语法,用来告诉驱动如何去访问数据库.在这个例子中,我们是连接到了本地MySQL实例中的"hello"数据库.
+3. 通常你应该(绝大多数情况下)检查和处理`database/sql`所有操作返回的`error`.我们将在以后讨论在特定条件下也可不处理的情况.
+4. 通常情况下,`sql.DB`的生命周期不会超出`function`的范围,此时应该添加`defer db.Close()`
+
+与直觉不同,`sql.Open()`并不会建立任何与数据库的连接,也不验证驱动的连接参数.它只是简单地准备好数据库抽象以备后用.第一个实际意义上的与数据库的连接会被延迟到第一次使用时才建立.如果你想立刻检查数据库是否可用(比如,检查你是否可以建立网络连接并登入数据库),可使用`db.Ping()`,且记得检查`error`.
+
+```go
+err = db.Ping()
+if err != nil {
+ // do something here
+}
+```
+
+虽然习惯上,当使用完数据库时需`Close()`掉数据库,然而,**`sql.DB`对象是被设计成支持长连接的**.请不要频繁地`Open()`和`Close()`数据库.相反,为每一个需要访问的数据库创建**一个**其独有的`sql.DB`对象,在程序访问完数据库前一直持有它.在需要时传递它,或让它全局可用,但是要确保是打开的.还有,不要在短暂的function中使用`Open()`和`Close()`,而是通过将`sql.DB`作为参数传入短暂function的方式代替.
+
+如果不将`sql.DB`视为长期对象,你将会碰到诸如此类的问题,比如无法复用和共享连接,网络资源失控,因为许多tcp连接停留在`TIME_WAIT`状态而导致间断性失败.此类问题意味着你没用按照`database/sql`的设计意图来使用它.
+
+现在是时候使用`sql.DB`对象了.
\ No newline at end of file
diff --git a/tutorial/connection-pool.md b/tutorial/connection-pool.md
new file mode 100755
index 0000000..a6759df
--- /dev/null
+++ b/tutorial/connection-pool.md
@@ -0,0 +1,12 @@
+# 连接池
+
+`database/sql`包中有一个基础的连接池,但并没有很好的方法去控制和监控它,然而下面的一些知识还是会对你有所帮助:
+
+* 连接池意味着在一个数据库中执行两个连续的语句时,可能会打开两个连接并分别执行它们.这是程序员相当常见的困惑,"为什么他们的代码执行不正确".例如,`LOCK TABLES`后紧跟的一个`INSERT`操作会被阻塞,因为`INSERT`是在一个没有持有表锁的连接上.
+* 在需要时,且当前池中没有可用的连接时,连接池会新建连接.
+* 默认时,连接池没有数量限制.如果你试图一次做很多事时,你可以创建任意数量的连接.这可能会导致数据库返回像"too many connections."这样的错误.
+* 在Go1.1及以后版本中,可使用`db.SetMaxIdleConns(N)`来限制连接池中空闲连接的数量.但这不会限制连接池的大小.
+* 在Go1.2.1及更新版本中,可`db.SetMaxOpenConns(N)`来限制连接池中可用连接的数量. 但在1.2版本中使用`db.SetMaxOpenConns(N)`会碰到一个[死锁bug](https://groups.google.com/d/msg/golang-dev/jOTqHxI09ns/x79ajll-ab4J) ([fix](https://code.google.com/p/go/source/detail?r=8a7ac002f840)).
+* 连接的回收速度相当快.通过将`db.SetMaxIdleConns(N)`设置为一个较高值可以缓解这个现象,有助于连接的复用.
+* 保持空闲连接过久会碰到问题(比如在微软Azure上的MySQL上的这个[issue](https://github.com/go-sql-driver/mysql/issues/257)).如果你遇到由于连接空闲太久而引发超时时,试试`db.SetMaxIdleConns(0)`.
+* 你也可以通过`db.SetConnMaxLifetime(duration)`来设置连接的超时时间(时间应小于数据库配置的连接超时时间),因为重用已长期存在的连接可能会导致网络问题.该参数会延迟关闭未使用的连接,即关闭已超时的连接可能会延迟.
\ No newline at end of file
diff --git a/tutorial/errors.md b/tutorial/errors.md
new file mode 100755
index 0000000..705e35e
--- /dev/null
+++ b/tutorial/errors.md
@@ -0,0 +1,125 @@
+# 错误处理
+
+几乎所有`database/sql`操作返回的参数的最后一个均是`error`类型.你应该经常检查这些错误,而不是忽略它们.
+
+只有少数几个地方得到的错误是例外,或者你可能需要知道更多的东西.
+
+遍历结果集时得到的错误
+================================
+
+考虑一下下面的代码:
+
+```go
+for rows.Next() {
+ // ...
+}
+if err = rows.Err(); err != nil {
+ // handle the error here
+}
+```
+
+`rows.Err()`中的错误可能是遍历`rows.Next()`时得到的各种错误.循环可能因一些原因会比预计提前退出,所以你必须检查循环是否正常结束.异常终止会自动调用`rows.Close()`,而且,它多次调用也是无害的.
+
+关闭结果集时得到的错误
+==============================
+
+如前面所述,当你提前退出循环时,你应该显示地关闭`sql.Rows`.如果循环是正常或碰到错误退出时,其会自动关闭,但你可能会错误地这么做:
+
+```go
+for rows.Next() {
+ // ...
+ break; // whoops, rows is not closed! memory leak...//哎呦,rows没有关闭!内存泄露...
+}
+// do the usual "if err = rows.Err()" [omitted here]...//像平常一样,"if err = rows.Err()"[这里省略]
+// it's always safe to [re?]close here://这里通常能安全地执行rows.Close():
+if err = rows.Close(); err != nil {
+ // but what should we do if there's an error?//但是如果碰到错误那要怎么做?
+ log.Println(err)
+}
+```
+
+`rows.Close()`返回的错误是一般规则(最好要捕获和检查所有数据库操作的错误)的唯一的例外情况.如果`rows.Close()`返回一个错误,目前还没有明确的做法来处理这个错误.记录错误消息或引发panic可能是唯一的明智之举,如果你认为这不可取,那么也许你应该忽略这个错误.
+
+运行QueryRow()时得到的错误
+======================
+
+请考虑如下获取单行的代码:
+
+```go
+var name string
+err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Println(name)
+```
+
+如果没有`id = 1`的用户呢?那么结果集就会是空的.而且`.Scan()`也就不能将查询结果存入变量`name`.这又会引发什么呢?
+
+Go中定义了一个名为`sql.ErrNoRows`的特殊错误常量.当结果集为空时,`QueryRow`就会返回它.这需要作为大多数情况中的特例来处理.一个空的结果集往往不应该被认为是应用的错误.如果你不检查这个特例的错误,那么将会引发应用出错,而这并不符合你的预期.
+
+查询的错误会延迟到调用`Scan()`时才返回.所以上面的代码最好这样写:
+
+```go
+var name string
+err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
+if err != nil {
+ if err == sql.ErrNoRows {
+ // there were no rows, but otherwise no error occurred
+ } else {
+ log.Fatal(err)
+ }
+}
+fmt.Println(name)
+```
+
+有人可能会问,为什么空结果集会被当作一个错误来看待.空结果集本身没错.原因在于,`QueryRow()`方法需要使用这个特例来告诉调用者其是否确实返回了一行.没有它,`Scan()`将不做任何事,而你也不会意识到你的变量根本没从数据库中获取到任何值.
+
+只有当你使用`QueryRow()`时,才会碰到这个错误.如果你在其他地方碰到了,那说明你在哪里做错了.
+
+定义针对特定数据库的错误
+====================================
+
+你很容易像下面那样写代码:
+
+```go
+rows, err := db.Query("SELECT someval FROM sometable")
+// err contains:
+// ERROR 1045 (28000): Access denied for user 'foo'@'::1' (using password: NO)
+if strings.Contains(err.Error(), "Access denied") {
+ // Handle the permission-denied error
+}
+```
+
+不要这样做.例如,该string的值可能会根据数据库采用不同的语言(中/英语)来发送错误信息而改变.最好和特定错误中预先定义的数值编号比较.
+
+然而,这种机制在驱动之间是有所差异的.这是因为驱动并不是`database/sql`本身的一部分.在使用本教程的MySQL驱动时,你可以这么做:
+
+```go
+if driverErr, ok := err.(*mysql.MySQLError); ok { // Now the error number is accessible directly
+ if driverErr.Number == 1045 {
+ // Handle the permission-denied error
+ }
+}
+```
+
+同样,这里的`MySQLError`类型是由该特定驱动提供的,而且`.Number`字段在不同驱动间可能不同.数值是从MySQL的错误信息中提取的,因此是数据库特有的,而不是驱动.
+
+这代码还是丑陋的.相对于1045,一个特殊的数值,这是代码味道(术语).一些驱动(尽管不包括MySQL的驱动,至于原因那就离题了)会提供错误标识符的列表.例如Postgres的驱动`pq`就有,在[error.go](https://github.com/lib/pq/blob/master/error.go)中.扩展包[MySQL错误码](https://github.com/VividCortex/mysqlerr)是由VividCortex维护的.使用这些列表,上面的代码最好这样写:
+
+```go
+if driverErr, ok := err.(*mysql.MySQLError); ok {
+ if driverErr.Number == mysqlerr.ER_ACCESS_DENIED_ERROR {
+ // Handle the permission-denied error
+ }
+}
+```
+
+处理连接错误
+==========================
+
+如果你的连接被丢弃,杀死或者出错了呢?
+
+发生时,你无需实现任何逻辑,只要重新尝试失败的语句即可.[连接池](connection-pool.html)作为`database/sql`的一部分,内置了错误处理.如果你执行一个查询或其他语句时,底层连接出现了问题,Go会重新打开一个新连接(或使用连接池中的其他连接)再次尝试,且至多尝试10次.
+
+然而,这也会导致一些意想不到的情况.当一些错误发生时,某些错误可能会被重试.这可能也是驱动的一种特性.其中一个发生在MySQL驱动中的例子就是,当使用`KILL`去取消一个不想要的语句(比如一个长查询)时,可能会被重试多达10次.
\ No newline at end of file
diff --git a/tutorial/importing.md b/tutorial/importing.md
new file mode 100755
index 0000000..fa835c4
--- /dev/null
+++ b/tutorial/importing.md
@@ -0,0 +1,21 @@
+# 导入数据库驱动
+
+
+为了使用`database/sql`,你不仅需要这个package本身,还需要针对特定数据库的驱动.
+
+通常情况下,你无需直接使用驱动包,虽然有些驱动会鼓励你这么做.(在我看来,这不是一个好主意).相反,如果可以的话,你应该只引用`database/sql`中定义的类型.这可以避免你的代码依赖该驱动,而你只需修改少量代码就可使用其他驱动去访问你的数据库了.
+
+在这个文档中, 我们将使用@julienschmidt和@arnehormann提供的优秀的[MySQL驱动](https://github.com/go-sql-driver/mysql)来演示.
+
+将下面的代码添加到你的Go源码文件的顶部:
+
+```go
+import (
+ "database/sql"
+ _ "github.com/go-sql-driver/mysql"
+)
+```
+
+注意,我们通过`_`别名,以匿名方式导入驱动,所以在我们的代码中,驱动的导出名称是不可见的.暗地里,驱动将自己注册到`database/sql`,但一般而言,驱动不会再做其它事情了.
+
+现在你已经准备好去访问数据库了.
\ No newline at end of file
diff --git a/tutorial/index.md b/tutorial/index.md
new file mode 100755
index 0000000..bcac688
--- /dev/null
+++ b/tutorial/index.md
@@ -0,0 +1,8 @@
+# 介绍
+
+Go通常使用[database/sql package](http://golang.org/pkg/database/sql/)去操作SQL或类SQL数据库.它为基于行导向的数据库即传统关系数据库(RDBMS)提供了轻量级的接口.本网站罗列了其大部分的用法.
+
+为什么需要这个? database/sql的文档说明它可以做什么,却没有说明如何使用.而我们大多数人希望有一个快速参考和入门教程来了解具体用法而不是只罗列功能.欢迎你的贡献,请发送pull requests到[这里](https://github.com/meilihao/go-database-sql-tutorial_zh-CN)
+
+ps:
+- [当前golang支持的SQLDriver](https://github.com/golang/go/wiki/SQLDrivers)
diff --git a/tutorial/modifying.md b/tutorial/modifying.md
new file mode 100755
index 0000000..8d4f1b3
--- /dev/null
+++ b/tutorial/modifying.md
@@ -0,0 +1,66 @@
+# 修改数据和使用事务
+
+现在我们已经准备好如何去修改数据和使用事务了.如果你已经习惯了使用一个`statement`对象来获取或更新数据,那么你会发现,两者在Go中有着明显的区别,其被人为地区分了出来.
+
+修改数据的语句
+===========================
+
+`Exec()`,最适合与预编译语句一起使用来完成`INSERT`,`UPDATE`, `DELETE`,以及其他不用返回行的语句.下面的例子将演示如何插入一行并且检查操作返回的元数据.
+
+```go
+stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
+if err != nil {
+ log.Fatal(err)
+}
+res, err := stmt.Exec("Dolly")
+if err != nil {
+ log.Fatal(err)
+}
+lastId, err := res.LastInsertId()
+if err != nil {
+ log.Fatal(err)
+}
+rowCnt, err := res.RowsAffected()
+if err != nil {
+ log.Fatal(err)
+}
+log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)
+```
+
+执行语句会生成一个`sql.Result`,通过它可访问返回语句的元数据:最新的插入ID和受影响的行数.
+
+如果你不关心执行的返回结果呢?如果你只想执行一个语句,再检查是否有错误,忽略其返回结果呢?那么下面的两个语句是否做了同一件事?
+
+```go
+_, err := db.Exec("DELETE FROM users") // OK
+_, err := db.Query("DELETE FROM users") // BAD
+```
+
+答案是否定的.它们并不是一样的,而且你也**不能**像这样使用**`Query()`**.`Query()`会返回一个`sql.Rows`,在`sql.Rows`未关闭前会一直占用连接.
+因为也许存在未读的(多行)数据,所以这个连接会一直被使用.在上面的例子中,这种连接**永远**都不会被释放.gc最终会关闭底层的`net.Conn`,但这会很耗时.此外,`database/sql`包也会追踪连接池中的这个连接,期待你在某一时刻会释放它从而让这个连接再使用.
+因此,这种反模式很容易导致资源失控(比如,太多的连接).
+
+使用事务
+=========================
+
+在Go中,事务的本质是一个对象,可存储与数据库的连接.它允许你做目前你所接触过的所有操作,但必须在同一个连接中完成.
+
+通过调用`db.Begin()`来开启一个事务,再通过调用变量`Tx`的`Commit()`和`Rollback()`方法来关闭事务.在其内部,`Tx`从连接池中获取一个连接,存起来以备这个事务使用.Tx的方法和你调用数据库时的方法是一一对应的,比如`Query()`等等.
+
+在事务中创建的预编译语句也是绑定在这个事务上的.(不能在事务外使用,同样的,在数据库句柄上创建的(即事务外创建的)预编译语句也不能在事务中使用).可在[预编译语句](prepared.html)中了解更多内容.
+
+请不要将事务的`Begin()`和`Commit()`函数与SQL语句中的`BEGIN`和`COMMIT`混淆.这会引发下面中的坏结果:
+
+* `Tx`对象会一直保持打开状态,而不会将连接释放回连接池.
+* 数据库的状态不再和代码中与其对应的变量的状态同步.
+* 你会认为你是在事务内部的连接上执行查询,实际上Go为你隐式地创建了多个连接,而且这些语句并不属于这个事务.
+
+在事务内操作时,你应当小心,不要使用`Db`变量,而是全部使用你调用`db.Begin()`时创建的`Tx`变量.`Db`不属于事务,`Tx`才是.如果你再调用`db.Exec()`或类似的,这些操作会发生在事务外的其他连接上.
+
+如果你需要使用多种语句来修改连接的状态,那么你就需要`Tx`,即使你自身不想使用事务.例如:
+
+* 创建临时表,这些临时表只对一个连接可见.
+* 设置变量,比如MySQL的语法`SET @var := somevalue`.
+* 修改连接的参数,比如字符集和超时时间.
+
+如果你想做这些事,那么你就必须在单一连接中进行,而在Go中唯一的做法就是使用事务.
\ No newline at end of file
diff --git a/tutorial/nulls.md b/tutorial/nulls.md
new file mode 100755
index 0000000..131c2eb
--- /dev/null
+++ b/tutorial/nulls.md
@@ -0,0 +1,43 @@
+# 使用Null
+
+可空类型列是恼人的,它会导致代码变得丑陋.如果可以,你应该尽量避免.如果不行,那你应该使用`database/sql`中定义的特殊类型或自定义类型来处理.
+
+bool,string,integers(数值),floats(浮点数)类型都有对应的空类型.下面是如何使用它们:
+
+```go
+for rows.Next() {
+ var s sql.NullString
+ err := rows.Scan(&s)
+ // check err
+ if s.Valid {
+ // use s.String
+ } else {
+ // NULL value
+ }
+}
+```
+
+不光可空类型的局限性,为了让人避免使用可空类型列,就需要更多令人信服的理由:
+
+1. 没有`sql.NullUint64`和`sql.NullYourFavoriteType`类型,你需要自己定义这些.
+1. 空特性相当棘手,也是不可预期的.如果你认为它不可能是null,那你就错了,而你的程序则会崩溃,也许在碰到它们之前你很少能够发现这种错误.
+1. Go有一个很好的特性就是每个变量都有一个可用的默认零值.这对可空类型却行不通.
+
+如果你需要用自定义类型来处理可空类型列,那么你可复制`sql.NullString`的设计来实现它.
+
+如果你的数据库无法避免使用`NULL`,那么你就应该使用大多数数据库都支持的`COALESCE()`. 以下可能就是你可以使用的东西,它不会引入`sql.Null`类型.
+
+```go
+rows, err := db.Query(`
+ SELECT
+ name,
+ COALESCE(other_field, '') as other_field
+ WHERE id = ?
+`, 42)
+
+for rows.Next() {
+ err := rows.Scan(&name, &otherField)
+ // ..
+ // If `other_field` was NULL, `otherField` is now an empty string. This works with other data types as well.
+}
+```
\ No newline at end of file
diff --git a/tutorial/overview.md b/tutorial/overview.md
new file mode 100755
index 0000000..52eb09e
--- /dev/null
+++ b/tutorial/overview.md
@@ -0,0 +1,15 @@
+# 概述
+
+在Go中,为了访问数据库,你需要使用`sql.DB`.你可使用这个类型来创建语句和事务,执行查询以及获取结果.
+
+首先,你需要知道**`sql.DB`不是一个数据库连接**.它也不是任何特定数据库软件中的database或schema概念的映射.它是一个抽象的接口,是一种数据库存在的形式.数据库可以是形形色色的,比如本地文件,通过网络连接的,在内存或进程中的.
+
+`sql.DB`在幕后执行着一些重要任务:
+
+* 它通过驱动打开和关闭对实际数据库的连接.
+* 它管理着一个连接池,连接可以是上面提到的多种形式.
+
+`sql.DB`被抽象成你无需关心如何去管理对数据库的并发访问.当你使用一个连接去执行任务时其会被标记成在使用状态,当它不再被使用时,则会放回到可用的连接池中.
+这样会造成一种结果,**如果释放连接失败,则会导致`db.SQL`打开很多连接**,从而导致资源失控(太多的连接,太多打开的文件的句柄,缺少可用的网络端口等).我们将在以后进一步讨论这个话题.
+
+创建`sql.DB`后,你就可使用它来查询数据库,以及创建语句和事务.
\ No newline at end of file
diff --git a/tutorial/prepared.md b/tutorial/prepared.md
new file mode 100755
index 0000000..103d57f
--- /dev/null
+++ b/tutorial/prepared.md
@@ -0,0 +1,77 @@
+# 使用预编译语句
+
+在Go中使用预编译语句有这些好处:安全,高效和便捷.但它们的实现方式可能和你的习惯有点不同,特别是它们需要和`database/sql`的一些内部功能协调工作.
+
+预编译的语句和连接
+===================================
+
+在数据库层面,一条预编译语句会绑定一个单独的连接.典型的流程是,客户端发送一条带占位符的SQL语句给数据库进行预编译,服务器再返回该语句的ID,然后客户端通过发送ID和参数来执行语句.
+
+然而在Go中,连接不是直接暴露给`database/sql`包的使用者.你不能在一个连接上预编译语句.你需要在`DB`或`Tx`中使用.而且`database/sql`有一些方便的功能比如自动重试.由于这些原因,在驱动层面需要关联预编译语句和连接,其隐藏在你的代码中.
+
+它是这样工作的:
+
+1. 当你要预编译一个语句时,需从连接池中获取一个连接.
+2. `Stmt`对象记住使用了哪个连接.
+3. 当你执行`Stmt`时,它会尝试启用这个连接.如果它不可用,比如已是关闭或正在使用时,它会从连接池中重新获取另外一个连接,**然后在新连接上重新预编译语句**
+
+因为语句在原始连接被占用时可以被重新预编译,在高并发的数据库中,这可能使得很多连接被使用或者创建了大量的预编译语句.这会导致明显的语句泄露,正在预编译的语句和要重新预编译的语句会比你想象的更多,甚至达到数据库对语句数量限制的上限.
+
+避免预处理语句
+============================
+
+Go会在幕后为你创建预编译语句.比如`db.Query(sql, param1, param2)`,其作用是预编译一个sql,传入参数执行它,最后关闭语句.
+
+有时候,预编译语句不是你想要的.有如下几种情况:
+
+1. 数据库不支持预编译语句.例如当使用MySQL驱动时,你可以连接到MemSQL和Sphinx上,因为它们支持MySQL的wire协议.但是它们不支持"binary"协议,其中就包含预编译语句,它们会因出现混乱而失败.
+2. 语句不能被重用使得使用它们有些不值,而且也需要用其他方式处理安全问题,所以这些性能开销是不需要的.在[VividCortex的博客](https://vividcortex.com/blog/2014/11/19/analyzing-prepared-statement-performance-with-vividcortex/)上就有这样的例子.
+
+如果你不想使用预编译语句,那么你需要使用`fmt.Sprint()`或类试的方式来拼接SQL,将其作为唯一的参数传入`db.Query()`或`db.QueryRow()`.还有你的驱动需要支持纯文本的查询方式,其是通过Go1.1中添加的`Execer`和`Queryer`接口来支持的,[文档在这里](http://golang.org/pkg/database/sql/driver/#Execer).
+
+在事务中使用预编译语句
+===================================
+
+`Tx`会创建和绑定预编译语句,所以上文关于预编译的警告并不适用于这里.当你使用`Tx`对象时,你的操作会被映射到底层的有且仅有的一个连接上.
+
+这也意味着预编译语句是在`Tx`内创建的,不可单独使用.同样,在`DB`上创建的预编译语句也不能在事务中使用,因为它们会和不同的连接绑定.
+
+若要在`Tx`中使用事务外定义的预编译语句,那么你可以使用`Tx.Stmt()`,它会通过事务外的预编译语句新建一个事务特有的语句.即它通过利用现有的预编译语句,设置连接到该事务,然后每次执行时预编译一次所有的语句.即使`database/sql`源码中的TODO清单指明会改进它,但这些操作和实现还是不受欢迎的;我们建议不要这样用.
+
+在事务中使用预编译语句时必须谨慎.考虑下例:
+
+```go
+tx, err := db.Begin()
+if err != nil {
+ log.Fatal(err)
+}
+defer tx.Rollback()
+stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")
+if err != nil {
+ log.Fatal(err)
+}
+defer stmt.Close() // danger!
+for i := 0; i < 10; i++ {
+ _, err = stmt.Exec(i)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+err = tx.Commit()
+if err != nil {
+ log.Fatal(err)
+}
+// stmt.Close() runs here!
+```
+
+在Go1.4之前,关闭`*sql.Tx`时会将关联的连接释放回连接池,但关闭预编译语句会延迟到这(tx.Close)**之后**进行,这可能会导致并发地访问底层连接,使得连接的状态出现不一致.如果你使用Go1.4或之后的版本,你应该确保在事务提交或回滚前关闭statement.[该issue](https://github.com/golang/go/issues/4459)已在Go1.4的[CR131650043](https://codereview.appspot.com/131650043)中被修复.
+
+参数占位符的语法
+============================
+
+预编译的参数占位符时数据库特有的,例如,比如MySQL,PostgreSQL和Oracle的:
+
+| MySQL | PostgreSQL | Oracle |
+| :---------------: | :-----------------: | :-------------------------: |
+| WHERE col = ? | WHERE col = $1 | WHERE col = :col |
+| VALUES(?, ?, ?) | VALUES($1, $2, $3) | VALUES(:val1, :val2, :val3) |
\ No newline at end of file
diff --git a/tutorial/references.md b/tutorial/references.md
new file mode 100755
index 0000000..5d735c6
--- /dev/null
+++ b/tutorial/references.md
@@ -0,0 +1,13 @@
+# 相关阅读和资源
+
+这里有一些我们找到的额外资源会对你有所帮助.
+
+* [http://golang.org/pkg/database/sql/](http://golang.org/pkg/database/sql/)
+* [http://jmoiron.net/blog/gos-database-sql/](http://jmoiron.net/blog/gos-database-sql/)
+* [http://jmoiron.net/blog/built-in-interfaces/](http://jmoiron.net/blog/built-in-interfaces/)
+* The VividCortex blog, e.g. [transparent encryption](https://vividcortex.com/blog/2014/11/11/encrypting-data-in-mysql-with-go/)
+
+我们希望这个网站能为你提供帮助.如果你有任何改进的建议,请发送pull requests或开issue提交到
+[go-database-sql-tutorial_zh-CN](https://github.com/meilihao/go-database-sql-tutorial_zh-CN).
+
+本文英文原版在[https://github.com/VividCortex/go-database-sql-tutorial](https://github.com/VividCortex/go-database-sql-tutorial).
\ No newline at end of file
diff --git a/tutorial/retrieving.md b/tutorial/retrieving.md
new file mode 100755
index 0000000..196f48d
--- /dev/null
+++ b/tutorial/retrieving.md
@@ -0,0 +1,123 @@
+# 获取结果集
+
+有几种常用的操作来从数据库中获取结果集:
+
+1. 执行一个查询,返回结果行
+2. 预编译一个可重复使用的`statement`,多次执行它,然后销毁它.
+3. 执行一个一次性的`statement`,因不会重复使用而无需预编译.
+4. 执行一个查询返回仅且返回一行.这是某些情况下的快捷方法.
+
+Go的`database/sql`的函数名称是有实际意义的."**如果函数名称包含`Query`,则它被设计成向数据库发送一个请求,然后返回一个行的集合**,即使它是空集合.那些不会返回行的语句应该使用`Exec()`,而不是`Query`"
+
+从数据库获取数据
+===============================
+
+让我们看一个如何查询数据库并处理结果的例子.我们查询`users`表中`id`为1的用户,并打印出该用户的`id`和`name`.使用`rows.Scan()`时,我们会将结果赋值给变量,一行一次.
+
+```go
+var (
+ id int
+ name string
+)
+rows, err := db.Query("select id, name from users where id = ?", 1)
+if err != nil {
+ log.Fatal(err)
+}
+defer rows.Close()
+for rows.Next() {
+ err := rows.Scan(&id, &name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Println(id, name)
+}
+err = rows.Err()
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+下面是关于上面代码的说明:
+
+1. 我们使用`db.Query()`发送查询给数据库,需像平时一样检查错误.
+2. 使用defer `rows.Close()`.这一步很重要.
+3. 使用`rows.Next()`来遍历结果集.
+4. 使用`rows.Scan()`来读取每一行的列,将结果放到变量中.
+5. 在遍历完结果集后仍需检查错误.
+
+在Go中,这几乎是获取数据的唯一操作方式了.例如,你不能以一个map类型的形式去获得一行的数据.这是因为Go中所有的类型都是强类型.如上面代码所示,你需要创建正确类型的变量再传入其指针.
+
+这里有部分内容很容易出错,会引发不好的后果.
+
+* 在`for rows.Next()`循环结束后你应该检查错误.如果在循环中碰到错误,你也不能忽略.记住,不要假设你的程序可以在循环中遍历完所有的行.
+* 其次,只要有任一`rows`的结果集被打开,那么这个连接就是被占用的,不能再用于其他查询.这意味着在连接池中不能再使用它.如果你用`rows.Next()`遍历所有行时,在读取最后一行后,`rows.Next()`将会碰到一个内部错误名为EOF,然后为你调用`rows.Close()`[注:go1中存在相关代码,go1.4.2中已删除].但是,如果出于某种原因退出了循环,比如,程序过早`return`等等,那么`rows`就不会被关闭,导致连接还是打开状态.(尽管`rows.Next()`碰到错误会返回false且会自动关闭`rows`).这很容易引发资源失控.
+* 如过rows已关闭,那么再调用`rows.Close()`将是一个无害的空操作,所以你可以重复调用.注意,为了避免`runtime panic`,我们应该先检查错误,无错时再调用`rows.Close()`.
+* 你应该**总是使用`defer rows.Close()`**,即使你在循环结束后会显示的调用`rows.Close()`,这将是一个不错的习惯.
+* 请不要在循环中使用`defer`,`defer`语句会延迟到函数结束时才执行,所以不要在耗时的函数中使用它.如果你这样做了,你的程序将会逐渐消耗内存.如果你要在循环中重复查询并使用结果集,那么循环中你应该使用`rows.Close()`而不是`defer rows.Close()`
+
+Scan()如何工作
+================
+
+当你遍历行,将所得内容放入指定变量时,Go已经在幕后帮你做了类型转换的工作.它是基于目标变量的类型.了解到这一点可以帮你理清你的代码,并且有助于避免重复性的工作.
+
+例如,假如你选择表中的一些行,其包含定义类型是string的列,比如`VARCHAR(45)`或其他类似的列.然而你碰巧知道,该表总是包含了数字.如果你传入一个string类型的指针,Go将会把bytes转换为string.现在你可使用`strconv.ParseInt()`或类似的方法将string转成数值了.你必须检查SQL操作中的错误,以及转换成整数时的错误.这是复杂而单调乏味的工作.
+
+或者,你只要将一个整数类型的指针传入`Scan()`.Go会检测到并调用`strconv.ParseInt()`来处理.如果转换时碰到了一个错误,那么`Scan()`将返回它.你的代码可以更加短小精悍.这是`database/sql`推荐使用的方法.
+
+预编译查询
+=================
+
+通常,为了可重复使用,你应该预编译一个查询.预编译好一个查询的结果就是一个预编译的"statement",执行语句时可用占位符绑定你提供的参数.这可比将参数拼接成字符串好多了,其可避免一些常见的问题(比如SQL注入)
+
+在MySQL中,参数的占位符是`?`,而在PostgreSQL中是`$N`,其中N是一个数值.SQLite两种都支持.在Oracle中占位符以冒号开头紧接着的是名称,比如`:param1`.这里我们使用`?`,因为我们是使用MySQL做演示的.
+
+```go
+stmt, err := db.Prepare("select id, name from users where id = ?")
+if err != nil {
+ log.Fatal(err)
+}
+defer stmt.Close()
+rows, err := stmt.Query(1)
+if err != nil {
+ log.Fatal(err)
+}
+defer rows.Close()
+for rows.Next() {
+ // ...
+}
+if err = rows.Err(); err != nil {
+ log.Fatal(err)
+}
+```
+
+在底层,`db.Query()`其实依次完成了预编译,执行和关闭一个预编译的`statement`,即与数据库交互了三次.如果你一不小心,
+你的程序与数据库的交互将增至3倍.有些驱动可避免这种情况,但不是所有的驱动都会这样做.更多内容请看[预编译语句](prepared.html).
+
+单行查询
+==================
+
+如果只查询至多一行时,你可以使用一个快捷方式来避免冗长的样板代码.
+
+```go
+var name string
+err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Println(name)
+```
+
+查询时的错误会延迟到调用`Scan()`后才返回.你也可以在预编译`statement`后调用`QueryRow()`.
+
+```go
+stmt, err := db.Prepare("select name from users where id = ?")
+if err != nil {
+ log.Fatal(err)
+}
+var name string
+err = stmt.QueryRow(1).Scan(&name)
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Println(name)
+```
\ No newline at end of file
diff --git a/tutorial/surprises.md b/tutorial/surprises.md
new file mode 100755
index 0000000..fe7ead4
--- /dev/null
+++ b/tutorial/surprises.md
@@ -0,0 +1,88 @@
+# 意外,反模式,限制
+
+尽管,一旦你适应`database/sql`后就会觉得它挺简单的,但你也会对它所支持的一些用例的精妙之处感到惊叹,而这对Go的核心库来说太常见了.
+
+## 资源耗尽
+
+正如前面提到的,如果你不慎重地使用`database/sql`,你就会自找麻烦.常见的问题有消耗更多的资源和资源无法有效的重复利用:
+
+* 打开和关闭数据库连接时可能会导致资源枯竭.
+* 读取所有行失败或调用`rows.Close()`失败会一直占用池里的连接.
+* 使用`Query()`来执行一些不会返回行的语句会一直占用池里的连接.
+* 不清楚[预编译语句](prepared.html)如何工作会导致数据库执行很多额外的操作.
+
+## 大uint64值
+
+这里有一个奇怪的错误.如果一个大容量的无符号整数的高字节有内容时,你就不能将其作为参数传入语句.
+
+```go
+_, err := db.Exec("INSERT INTO users(id) VALUES", math.MaxUint64) // Error
+```
+
+这里会抛出一个错误.使用`uint64`时请小心,开始时参数数值很小,不会引发错误,随着时间推移,终会碰到错误.
+
+## 连接状态不匹配
+
+一些操作会改变连接的状态从而引发问题,比如下面的两个原因:
+
+1. 连接的状态,比如是否在事务中运行,可以通过修改Go变量来实现.
+2. 你可能会假定你的查询是在同一个连接上进行的,实际却不是.
+
+例如,很多人习惯于通过`USE`语句来指定当前要操作的数据库.但在Go中,它只会影响到你正在使用的那个连接.除非在事务中,否则你认为跑在同一个连接上的语句,实际上可能跑在从连接池中取得的不同连接上,只是程序不会感知到这些变化罢了.
+
+此外,如果你修改了连接的状态,当它返回连接池时可能会潜在地污染其他代码(连接再次被使用时,其状态已改变).这也是为什么你不应该在SQL命令中直接使用`BEGIN`或`COMMIT`语句的原因之一.
+
+## 数据库的特定语法
+
+`database/sql`的API提供了对关系型数据库的抽象,但在特定的数据库和驱动的操作和语法上还是存在差异,比如[预编译语句](prepared.html).
+
+## 多结果集
+
+Go的驱动不支持任何形式的在单个查询中获得多个结果集的功能,即使有批量操作的[功能需求](https://github.com/golang/go/issues/5171)被提出,比如批量复制.
+
+此外这意味着,当一个存储过程返回多个结果集时,程序就不能正常运行了.
+
+## 调用存储过程
+
+调用存储过程是驱动的一种特性(但目前在MySQL的驱动上还不能工作).其意味着你可通过调用一个简单的存储过程来得到一个结果集,比如执行如下代码:
+
+```go
+err := db.QueryRow("CALL mydb.myprocedure").Scan(&result) // Error
+```
+
+实际上它不能正常运行.你会得到这样的错误:`_Error 1312: PROCEDURE mydb.myprocedure can't return a result set in the given context_`.这是因为MySQL需要将连接设置成multi-statement模式,即使只是为了得到一个结果集,还有目前驱动不推荐这么做(可查看[这个issue](https://github.com/go-sql-driver/mysql/issues/66)).
+
+## 多语句支持
+
+`database/sql`没有明确的多语句支持,这意味着操作是否支持取决于后端(数据库).
+
+```go
+_, err := db.Exec("DELETE FROM tbl1; DELETE FROM tbl2") // Error/unpredictable result
+```
+
+数据库会按照自己的方式解析语句,比如是返回一个错误,是只执行第一个语句还是两个都会执行.
+
+同样,没法在事务中执行批量操作.在事务中,语句会被串行执行,在连接被释放给另一个语句使用前,执行得到的结果集必须被处理或关闭.不用事务的处理方式与这不同.在这种情况下,执行查询,遍历结果集和在循环中再次执行查询完全可能使用到一个新连接.
+
+```go
+rows, err := db.Query("select * from tbl1") // Uses connection 1
+for rows.Next() {
+ err = rows.Scan(&myvariable)
+ // The following line will NOT use connection 1, which is already in-use
+ db.Query("select * from tbl2 where id = ?", myvariable)
+}
+```
+
+但因为事务只有一个绑定的连接,所以上面的情况不会在事务中发生.
+
+```go
+tx, err := db.Begin()
+rows, err := tx.Query("select * from tbl1") // Uses tx's connection
+for rows.Next() {
+ err = rows.Scan(&myvariable)
+ // ERROR! tx's connection is already busy!
+ tx.Query("select * from tbl2 where id = ?", myvariable)
+}
+```
+
+虽然,Go不会尝试阻止你这样做.出于这个原因,在你尝试执行另一个语句前,如果没有释放执行上一个语句所占用的资源和清理自身,你最终可能会损坏这个连接.这也意味着事务中每一条语句的执行结果是通过一组独立的网络与数据库交互的.
\ No newline at end of file
diff --git a/tutorial/varcols.md b/tutorial/varcols.md
new file mode 100755
index 0000000..b5e8ad7
--- /dev/null
+++ b/tutorial/varcols.md
@@ -0,0 +1,46 @@
+# 使用未知列
+
+`Scan()`函数需要传入和目标变量数目相应匹配的数量.但当你不知道查询将返回什么时该怎么办?
+
+如果你不知道查询将返回多数列时,你可使用`Columns()`来得到列名的列表.再通过检查列表的长度来得到其总共查询到了多少列,然后你可以将一个正确长度的slice传入`Scan()`.例如,一些MySQL的分支在使用`SHOW PROCESSLIST`命令时会返回不同的列数,所以你必须为此做好准备否则你会碰到错误.下面是这么做的方法之一,当然还有其他的:
+
+```go
+cols, err := rows.Columns()
+if err != nil {
+ // handle the error
+} else {
+ dest := []interface{}{ // Standard MySQL columns
+ new(uint64), // id
+ new(string), // host
+ new(string), // user
+ new(string), // db
+ new(string), // command
+ new(uint32), // time
+ new(string), // state
+ new(string), // info
+ }
+ if len(cols) == 11 {
+ // Percona Server
+ } else if len(cols) > 8 {
+ // Handle this case
+ }
+ err = rows.Scan(dest...)
+ // Work with the values in dest
+}
+```
+
+如果你不知道定义该列用的类型,那么应该使用`sql.RawBytes`.
+
+```go
+cols, err := rows.Columns() // Remember to check err afterwards
+vals := make([]interface{}, len(cols))
+for i, _ := range cols {
+ vals[i] = new(sql.RawBytes)
+}
+for rows.Next() {
+ err = rows.Scan(vals...)
+ // Now you can check each element of vals for nil-ness,
+ // and you can use type introspection and type assertions
+ // to fetch the column into a typed variable.
+}
+```
\ No newline at end of file
From 7793e4f5b7997e5449c1974d7f5d2d602c30836b Mon Sep 17 00:00:00 2001
From: meilihao <563278383@qq.com>
Date: Sat, 16 Sep 2017 13:54:07 +0800
Subject: [PATCH 02/11] =?UTF-8?q?+=20=E8=BF=90=E8=A1=8C=E5=90=AF=E5=8A=A8?=
=?UTF-8?q?=E5=91=BD=E4=BB=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 7 +++++++
index.html | 1 +
2 files changed, 8 insertions(+)
diff --git a/README.md b/README.md
index fc13e3c..62fab30 100644
--- a/README.md
+++ b/README.md
@@ -3,5 +3,12 @@
- [原文](http://go-database-sql.org)
- [git-origin#1ec3d97 on 17 Mar 2016](https://github.com/VividCortex/go-database-sql-tutorial)
+## 运行
+```sh
+$ docsify serve go-database-sql-tutorial
+```
+
+> 依赖[文档生成工具 docsify](https://github.com/QingWei-Li/docsify)
+
## lisence
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
diff --git a/index.html b/index.html
index d315679..67b6870 100644
--- a/index.html
+++ b/index.html
@@ -43,4 +43,5 @@
+