本文翻译自《Opening a database handle》。
目录
database/sql
包减少了你管理连接的需要,从而简化了数据库访问。与许多数据访问API不同,使用database/sql
时,你不是显式地打开数据库连接、工作,然后关闭连接。相反,你的代码打开一个表示连接池的数据库句柄,然后使用该句柄执行数据访问操作,仅在需要释放资源时调用Close
方法,例如检索到的行或准备好的语句所持有的资源。
换句话说,由sql.DB
表示数据库句柄,用于处理连接,代表代码打开和关闭连接。当你的代码使用数据库句柄执行数据库操作时,这些操作可以并发地访问数据库。有关详细信息,请参阅管理数据库连接。
注意:你也可以使用一条专用的数据库连接。有关更多信息,请参阅使用专用连接。
除了database/sql
包中提供的API之外,Go社区还为所有最常见(以及许多不常见)的数据库管理系统(DBMS)开发了驱动程序。
打开数据库句柄时,你执行以下高层级的步骤:
1 找到一个驱动程序。
驱动程序在Go代码和数据库之间转换请求和响应。有关详细信息,请参阅查找和导入一个数据库驱动程序。
2 打开一个数据库句柄。
导入驱动程序后,可以打开访问特定数据库的一个句柄。有关详细信息,请参见打开一个数据库句柄。
3 确认一个数据库连接。
打开数据库句柄后,代码就可以检查这个数据库连接是否可用。有关详细信息,请参阅确认一个数据库连接。
你的代码通常不会显式地打开或关闭数据库连接——这是由数据库句柄完成的。然而,你的代码应该释放在此过程中获得的资源,例如包含查询结果的sql.Rows。有关详细信息,请参见释放资源。
查找和导入一个数据库驱动程序
你需要一个支持你正在使用的DBMS的Go语言的数据库驱动程序。要查找数据库的驱动程序,请参阅SQLDrivers。
为了使驱动程序可用于你的代码,你可以像导入另一个Go包一样导入它。下面是一个例子:
import "github.com/go-sql-driver/mysql"
请注意,如果你不直接从驱动程序包调用任何函数,例如当sql
包隐式使用它时,你需要使用一个空白导入,该导入在导入路径前加一个下划线:
import _ "github.com/go-sql-driver/mysql"
注意:作为最佳实践,避免使用数据库驱动程序自己的API进行数据库操作。相反,应该使用database/sql
包中的函数。这将有助于保持代码与DBMS的松散耦合,从而在需要时更容易切换到不同的DBMS。
打开一个数据库句柄
sql.DB
数据库句柄提供了单独或在事务中读取和写入数据库的能力。
你可以通过调用sql.Open
函数(使用连接字符串)或sql.OpenDB
函数(使用driver.Connector
)来获取数据库句柄。两者都返回一个指向sql.DB
的指针。
注意:请确保将你的数据库凭据放在Go源代码之外。有关更多信息,请参阅存储数据库凭据。
使用一个连接字符串
如果要使用连接字符串进行连接,请使用sql.Open
函数。字符串的格式会因你使用的驱动程序而异。
以下是MySQL的一个示例:
db, err = sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
log.Fatal(err)
}
然而,你可能会发现,以更结构化的方式捕获连接属性可以为你提供更具可读性的代码。具体细节因数据库驱动而异。
例如,你可以将前面的示例替换为以下示例,该示例使用MySQL驱动程序的Config
指定连接属性,并使用FormatDSN
方法构建连接字符串。
// 指定连接属性。
cfg := mysql.Config{
User: username,
Passwd: password,
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "jazzrecords",
}
// 获得一个数据库句柄。
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
使用一个连接器
当你想利用连接字符串中无法设置的特定于驱动程序的连接特性时,请通过sql.OpenDB
函数。每个驱动程序都支持自己的连接特性集,通常用于自定义特定于DBMS的连接请求。
将前面的sql.Open
示例改编为使用sql.OpenDB
,你可以使用以下代码创建一个数据库句柄:
// 指定连接属性。
cfg := mysql.Config{
User: username,
Passwd: password,
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "jazzrecords",
}
// 获取一个特定驱动的连接器
connector, err := mysql.NewConnector(&cfg)
if err != nil {
log.Fatal(err)
}
// 获取一个数据库句柄。
db = sql.OpenDB(connector)
处理错误
你的代码应该检查尝试创建句柄时是否出现了一个错误,例如使用sql.Open
函数。这不会是连接错误。相反,如果sql.Open
函数无法初始化句柄,则会出现一个错误。例如,如果它无法解析你指定的DSN,就可能会发生这种情况。
确认一个数据库连接
打开一个数据库句柄时,sql
包可能不会立即创建一个新的数据库连接。相反,它可能会在你的代码需要时创建这个连接。如果你不想立即使用数据库,并且想确认可以建立连接,请调用Ping
或PingContext
函数。
以下示例中的代码ping
数据库以确认连接。
db, err = sql.Open("mysql", connString)
// 确认是否能连接数据库成功。
if err := db.Ping(); err != nil {
log.Fatal(err)
}
存储数据库凭据
避免在Go源代码中存储数据库凭据,因为这可能会将数据库的内容暴露给其他人。相反,找到一种方法将它们存储在代码之外但可供使用的位置。例如,考虑一个秘密保管器应用程序,该应用程序存储凭据并提供一个API,代码可以使用该API来检索凭据,以便与DBMS进行身份验证。
一种流行的方法是在程序启动前将秘密信息存储在你的环境变量中,然后你的Go程序可以使用os.Getenv
函数:
username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASS")
这种方法还允许你自己设置用于本地测试的环境变量。
释放资源
尽管你没有使用database/sql
包显式地管理或关闭连接,但当不再需要资源时,你的代码应该会释放所获得的资源。这些包括sql.Rows
所拥有的资源,表示从查询返回的行数据,或者sql.Stmt
表示准备好的语句。
通常,通过延迟对Close
函数的调用来释放资源,以便在外部函数退出之前释放资源。
以下示例中的代码延迟调用Close
函数以释放sql.Rows
所拥有的资源。
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 循环遍历返回的行数据。