打开数据库句柄

database/sql 软件包通过减少您管理连接的需求来简化数据库访问。与许多数据访问 API 不同,使用 database/sql 时,您不会显式打开连接、执行工作,然后关闭连接。相反,您的代码会打开一个表示连接池的数据库句柄,然后使用该句柄执行数据访问操作,仅在需要释放资源(例如检索的行或已准备好的语句所持有的资源)时调用 Close 方法。

换句话说,由 sql.DB 表示的数据库句柄负责连接,代表您的代码打开和关闭连接。当您的代码使用句柄执行数据库操作时,这些操作会并发访问数据库。有关更多信息,请参阅 管理连接

注意:您还可以保留数据库连接。有关更多信息,请参阅 使用专用连接

除了 database/sql 软件包中提供的 API 外,Go 社区还为所有最常见(以及许多不常见)的数据库管理系统 (DBMS) 开发了驱动程序。

在打开数据库句柄时,请遵循以下高级步骤

  1. 找到驱动程序。

    驱动程序在 Go 代码和数据库之间转换请求和响应。有关详细信息,请参阅 找到并导入数据库驱动程序

  2. 打开数据库句柄。

    导入驱动程序后,可以为特定数据库打开句柄。有关详细信息,请参阅 打开数据库句柄

  3. 确认连接。

    打开数据库句柄后,代码可以检查连接是否可用。有关详细信息,请参阅 确认连接

代码通常不会显式打开或关闭数据库连接 - 这是由数据库句柄完成的。但是,代码应该释放它在过程中获取的资源,例如包含查询结果的 sql.Rows。有关详细信息,请参阅 释放资源

找到并导入数据库驱动程序

您需要一个支持您正在使用的 DBMS 的数据库驱动程序。要为您的数据库找到驱动程序,请参阅 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 方法 来构建连接字符串。

// Specify connection properties.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Get a database handle.
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
    log.Fatal(err)
}

使用连接器打开

如果您希望利用连接字符串中不可用的特定于驱动程序的连接功能,请使用 sql.OpenDB 函数。每个驱动程序都支持自己的一组连接属性,通常提供针对特定 DBMS 自定义连接请求的方法。

将前面的 sql.Open 示例改编为使用 sql.OpenDB,您可以使用如下代码创建一个句柄

// Specify connection properties.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Get a driver-specific connector.
connector, err := mysql.NewConnector(&cfg)
if err != nil {
    log.Fatal(err)
}

// Get a database handle.
db = sql.OpenDB(connector)

处理错误

您的代码应检查尝试创建句柄(例如使用 sql.Open)时出现的错误。这不会是连接错误。相反,如果 sql.Open 无法初始化句柄,您将收到错误。例如,如果它无法解析您指定的 DSN,就会发生这种情况。

确认连接

当您打开数据库句柄时,sql 包可能不会立即创建新的数据库连接。相反,它可能会在您的代码需要时创建连接。如果您不会立即使用数据库,并且希望确认可以建立连接,请调用 PingPingContext

以下示例中的代码对数据库执行 ping 操作以确认连接。

db, err = sql.Open("mysql", connString)

// Confirm a successful connection.
if err := db.Ping(); err != nil {
    log.Fatal(err)
}

存储数据库凭据

避免在 Go 源代码中存储数据库凭据,这可能会将数据库的内容泄露给他人。相反,请找到一种方法将它们存储在代码外部但代码可以访问的位置。例如,考虑使用存储凭据并提供 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()

// Loop through returned rows.