Go语言数据库驱动程序基础构建指南(四)

Using a sql.DB

正如前面所提到的,sql.DB是一个数据库的抽象。(一种常见的错误是把它理解成数据库的连接).它暴露了一系列可以用来和数据库通信的函数,在内部实现了一个连接池(这个概念贯穿本书),处理了大量繁琐和重复的工作,所有这些都可以安全地在多个goroutine里同时使用。

推荐你为所有数据库使用场景创建一个sql.DB对象,并且长时间保留,静态变量的做法很不错。因为它内含连接池,这样做并不意味着不断地创建和销毁。你应该让你的所有代码都可以使用到你的这个sql.DB的对象。

怎样创建sql.DB呢,就像前面章节提到的,你需要导入数据库驱动程序。后面再聊关于驱动程序的细节。现在,足以注意到驱动程序通常仅用于副作用。这就是我们将其导入名称绑定到_匿名变量的原因,因此它们的命名空间在你的代码中不可用。 总而言之,程序主文件中所需的必要导入如下:

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

再次使用我们最喜欢的MySQL驱动程序作为示例。

你只需要在某个文件中导入一次driver(驱动程序),通常是main.go或类似的文件(比如说你实现了对于数据库功能的封装,你就应该在包装器库中导入driver)。 从此处开始,你将与database/sql类型进行交互,它代表你与数据库交互,因此你无需直接访问driver。 在幕后,driver使用init()函数向database/sql注册自己。

现在,要实际创建sql.DB的实例,可以使用带有两个参数的sql.Open()。第一个是driver的名字。这是驱动程序向database/sql注册的字符串标识,通常与其包名相同,以避免混淆。

第二个参数是连接字符串,也被成为DSN(data source name)。这是特定于driver的,对于database/sql没有意义。它只是传递给你使用的driver,它可能包括TCP连接端点,Unix套接字,用户名和密码,文件名或你可以想到的任何其他内容。想了解具体的有关详细信息,请查看driver的文档。

连接池如何工作

此时你可能以为你有了一个数据库连接,其实并没有。你只是创建了一个对象并把它和driver关联起来。database/sql依赖driver创建和管理dirver的连接,并为他们管理连接池。连接池初始化时是空的,连接是在需要时懒启动的。连接池的理解非常重要因为它很大程度上影响你的程序的行为。这也是为什么我们要在这本书的伊始谈到这个问题。

连接池的概念非常好理解。当你调用一个需要和数据库交互的函数时,这个函数会先向连接池请求一个连接。如果连接池此时有空闲的连接,连接池就会把它分配给这个函数。如果没有空闲的连接,连接池就会创建一个新的,连接是属于这个函数的。当函数完成时,函数把连接返还给连接池,或者把它传递给下一个需要连接的对象,那个对象在结束时把连接返给连接池。

假设现在有一个名为dbsql.DB变量,你可以调用以下特定函数,处理方式如下:

  • db.Ping() 立即返回连接池的连接。
  • db.Exec() 立即返回连接池的连接,同时返回一个和连接关联的Result对象,它可用来检查Exec()的结果。
  • db.Query() 将连接的所有权传递给sql.Row对象,当你完全迭代所有行或调用.Close()方法时,该对象将连接释放回连接池中。
  • db.QueryRow() 将连接传递给sql.Row,它会在调用.Scan()的时候释放连接。
  • db.Begin() 把连接传递给sql.Tx,它会在.Commit()或者.Rollback()被调用时释放连接。

考虑到每一条连接都是需要时懒加载的,那怎么在使用sql.Open()创建数据库之后怎么验证sql.DB呢?db.Ping()就是为这个服务的,常用代码片段如下:

    db, err := sql.Open("driverName", "dataSourceName")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

现在你确定你的db是可用的了,因为Ping()创建了一个连接并且把它返回给数据库连接池了。

顺便说一句,上面的代码在某个方面并不是真正的常用代码。通常你会做一些更聪明的事情,而不仅仅是记录致命的错误。 但在本书中,我们将始终将log.Fatal(err)显示为实际错误处理的占位符。

连接池的结果是你不需要检查或处理连接失败的情况。如果在对数据库执行操作时连接失败,database/sql将为你处理。 在内部,当发现池中的连接已经死亡时,它会重试最多10次。它只是从池中取出另一个或打开一个新的。这意味着你的代码可以是干净的,没有杂乱的重试逻辑。

配置连接池参数

早期版本的Go并没有对连接池提供太多控制,但是在Go 1.2.1及更高版本中,有一些控制它的选项。(版本1.2的连接池中存在错误;请使用1.2.1及以上版本)。这些控制如下:

  • db.SetMaxOpenConns(n int) 这将设置池将对数据库打开的最大连接数。这包括正在使用的连接以及连接池池中空闲的连接。如果你调用函数请求来自连接池的连接,而此时连接池中没有空闲并且连接数已达到限制,那么你的请求将会被阻塞,可能会持续很长时间。连接数默认限制为0,表示无限制。

  • db.SetMaxIdleConns(n int) 设置释放后将在池中保持空闲的连接数。 默认值为0,这意味着连接在池中根本不会保持空闲状态:从服务中释放时它们将关闭。这可能导致许多连接被关闭并快速打开,这可能不是你想要的,因为创建和销毁都是开销比较大的操作。

关于连接池的关键事项是,由于你使用连接的方式以及如何配置池,可能会导致一些意料之外的事情发生:

  • 1.大量的连接thrash[不知道咋翻译,我理解为不断地创建新的数据库连接],造成额外的工作和导致延时。
  • 2.数据库连接数太多,导致数据库成了瓶颈,抱错。
  • 3.等待连接阻塞。
  • 4.如果连接池中有10个或更多死连接,则操作可能会失败,因为内置限制为10次重试,运气不好十次都找到死连接,就挂了。

大多数情况下,使用sql.DB的方式比配置连接池更能导致这些事件发生。我们将在本书的后面探讨这一点。现在,让我们继续下一个主题,从数据库中获取结果并使用它们做有用的事情!

你可能感兴趣的:(Go语言数据库驱动程序基础构建指南(四))