使用Go驱动包Go-MySQL-Driver连接MySQL数据库

如果您的应用侧使用Go语言,且数据库连接创建频繁(例如短连接场景)或连接数量较大(大于MySQL数据库的连接数限制),您可以参考本教程通过Go驱动包Go-MySQL-Driver连接RDS MySQL数据库,降低连接建立频率以减少数据库主线程的开销。此外,本文还提供连接后操作RDS MySQL数据库的方法。

说明

Go-MySQL-DriverGo语言的第三方数据库驱动包,支持连接MySQLMariaDB数据库,详情请参见Go-MySQL-Driver

使用限制

RDS MySQL实例的数据库版本仅支持5.7、8.0。

准备工作

  • 已下载并安装Go环境,建议安装Go 1.20及以上版本。

  • 已将客户端IP地址添加至RDS实例白名单中。具体操作,请参见设置白名单

    说明

    若您的应用程序部署在阿里云ECS服务器上,且ECSRDS实例位于同一地域和同一VPC,则无需设置IP白名单。

使用Go-MySQL-Driver连接数据库

1. 依赖引入

  1. Go项目的go.mod文件中,添加go-sql-driver依赖:

    require (
        github.com/go-sql-driver/mysql v1.8.1
    )
  2. .go文件中导入如下包:

    import (
        // 引入Go语言的 database/sql 包,它提供了通用的SQL接口
        "database/sql"
        // 引入fmt包,它是Go语言标准库中用于格式化输入输出的包
        "fmt"
        // 该驱动可在Go语言中连接和操作MySQL数据库
        "github.com/go-sql-driver/mysql"
        // 引入了Go语言的time包,用于处理时间相关的操作
        "time"
    )

2. 连接池初始化

.go文件的main方法中初始化连接池并配置以下参数,示例如下:

// 创建一个数据库连接
cfg := mysql.NewConfig()
cfg.User = "****"       //您需手动替换为实际数据库用户名
cfg.Passwd = "****"     //您需手动替换为实际数据库密码
cfg.Net = "tcp"         //连接类型为TCP,保持默认,无需您手动修改
cfg.Addr = "rm-2zefwjx1s8156******.mysql.rds.aliyuncs.com:3306" //您需手动替换为实际MySQL数据库连接地址和端口号
cfg.DBName = "****"     //您需手动替换为实际数据库名称

conn, err := mysql.NewConnector(cfg)
if err != nil {
    panic(err.Error())
}

db := sql.OpenDB(conn)
defer func(db *sql.DB) {
    err := db.Close()
    if err != nil {
        fmt.Printf("Error closing database connection: %v\n", err)
    }
}(db)
// 设置连接池参数
db.SetMaxOpenConns(20)                 // 设置连接池中最大打开的连接数,您可根据实际需要手动调整
db.SetMaxIdleConns(2)                  // 设置连接池中最大空闲的连接数,您可根据实际需要手动调整
db.SetConnMaxIdleTime(10 * time.Second) // 连接在连接池中的最长的空闲时间,您可根据实际需要手动调整
db.SetConnMaxLifetime(80 * time.Second) // 设置连接的最大生命周期,您可根据实际需要手动调整
说明

RDS MySQL数据库连接地址和端口的获取方法,请参见查看和管理实例连接地址和端口

附录:连接池关键参数配置

重要
  • 推荐配置“推荐配置的参数”模块,降低数据库运行风险;您可以选择性配置“可选择配置的参数模块”,以提升使用性能。

  • 为了最大程度地避免潜在的风险和不确定性,您在将新的参数值用于生产环境前,建议至少进行一轮完整的功能测试和性能测试,以确保系统稳定性和可靠性。

推荐配置的参数

参数名

含义

默认值

推荐值

说明

maxIdleTime

设置最大空闲时间(单位:分钟)。

0

10~30

  • 默认值0表示不超时。

  • 建议根据应用程序的实际需求配置该参数。可通过SetConnMaxIdleTime方法进行设置。

maxLifetime

设置连接的生命周期时间(单位:小时)。

0

1~8

  • 默认值0表示永久存在,即没有设置最大生命周期。

  • 设置该参数是为防止连接长时间存在,浪费资源。可通过SetConnMaxLifetime方法进行设置。

maxOpen

设置连接池中允许的最大连接数。

0

100

  • 默认值0表示没有限制。

  • 如果MaxIdleConns大于0且新的MaxOpenConns小于MaxIdleConns,则MaxIdleConns将被缩减以匹配新的MaxOpenConns限制。

  • 建议根据应用程序的实际需求配置该参数。可通过SetMaxOpenConns方法进行设置。

maxIdleCount

设置连接池中允许的最大空闲连接数。

2

20

  • 适当配置该参数可以预留一定数量的连接,快速处理突发增加的数据库请求。

  • 如果MaxOpenConns大于0但小于新的MaxIdleConns,则新的MaxIdleConns将被缩减以匹配MaxOpenConns的限制。

  • 可通过SetMaxIdleConns方法进行设置。

  • 如果设置传入的参数n <= 0,则不会保留空闲连接。

readTimeout

I/O读取的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)。

0

10000ms-60000ms

  • 默认值0表示没有超时限制。建议保持默认值,如果要修改此值,请根据实际业务调整。

  • 请使用Go标准库time.Duration类型直接赋值,通过预定义单位常量设置,例如cfg.readTimeout = 30*time.Second,避免使用字符串格式(例如30s)。

writeTimeout

I/O写入的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)。

0

10000ms-60000ms

  • 默认值0表示没有超时限制。建议保持默认值,如果要修改此值,请根据实际业务调整。

  • 请使用Go标准库time.Duration类型直接赋值,通过预定义单位常量设置,例如cfg.writeTimeout=30*time.Second,避免使用字符串格式(例如30s)。

可选择配置的参数

参数名

含义

默认值

推荐值

说明

timeout

连接建立的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)。

默认值是根据操作系统的默认值来设定。

3000ms

  • 请使用Go标准库time.Duration类型直接赋值,通过预定义单位常量设置,例如cfg.timeout=30*time.Second,避免使用字符串格式(例如30s)。

  • 创建时的配置,建议设置1~10秒之间,取决于网络质量高低,以及应用端与服务端的距离。

操作数据库

创建表

本文以创建一个名为 userinfo 的表为例演示。

_, err = db.Exec("create table  if not exists userinfo( uid int auto_increment, username varchar(20) not null default '', departname varchar(20) not null default '', created varchar(10) not null default '', primary key(uid) );")
if err != nil {
    fmt.Println("create table error ", err)
    return
}

写入数据

本文以在 userinfo 表中写入数据为例演示。

方法一:直接写入数据

stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
res, err := stmt.Exec("James", "Research", "2016-06-17")
id, err := res.LastInsertId()
if err != nil {
    panic(err)
}
fmt.Println(id)

方法二:通过绑定参数的方式写入数据

result, err := db.Exec("INSERT INTO userinfo (username, departname, created) VALUES (?, ?, ?)", "Linda", "Testing", "2016-06-21")
if err != nil {
    panic(err)
}
ids, err := result.LastInsertId()
fmt.Println(ids)

更新数据

本文以更新userinfo 表中的数据为例演示。

stmtUpdate, err := db.Prepare("UPDATE userinfo SET username=?, departname=?, created=? WHERE username=?")
if err != nil {
    fmt.Println("Prepare update statement error:", err)
    return
}
resUpdate, err := stmtUpdate.Exec("Robert", "Sales", "2024-09-23", "Linda")
if err != nil {
    fmt.Println(err)
}
rowCnt, _ := resUpdate.RowsAffected()
fmt.Println(rowCnt)

查询数据

本文以查询 userinfo 表中username=Robert的数据为例演示。

方法一:直接查询数据

rows, err := db.Query("SELECT username,departname,created FROM userinfo WHERE username=?", "Robert")
if err != nil {
    panic(err)
}
for rows.Next() {
 var username, departname, created string
 if err := rows.Scan(&username, &departname, &created); err != nil {
     fmt.Println(err)
 }
 fmt.Println("username:", username, "departname:", departname, " created:", created)
}
defer func(rows *sql.Rows) {
 err := rows.Close()
 if err != nil {
     fmt.Println(err)
 }
}(rows)

方法二:通过绑定参数的方式执行参数化查询

stmtQuery, err := db.Prepare("SELECT username,departname,created FROM userinfo WHERE username=?")
if err != nil {
    fmt.Println("prepare error", err)
    return
}
rowData, err := stmtQuery.Query("Robert")
if err != nil {
    fmt.Println("query data error", err)
    return
}
for rowData.Next() {
 var username, departname, created string
 if err := rowData.Scan(&username, &departname, &created); err != nil {
     fmt.Println(err)
 }
 fmt.Println("username:", username, "departname:", departname, "created:", created)
}
defer func(rows *sql.Rows) {
 err := rows.Close()
 if err != nil {
     fmt.Println(err)
 }
}(rowData)

删除数据

本文以删除userinfo 表中username=James的数据为例演示。

delStmt, _ := db.Prepare("DELETE FROM userinfo WHERE username=?")
resultDel, err := delStmt.Exec("James")
if err != nil {
    panic(err)
}
rowAffect, _ := resultDel.RowsAffected()
fmt.Println("Data deletion completed.", rowAffect)

完整示例

package main

import (
    // 引入Go语言的 database/sql 包,它提供了通用的SQL接口
    "database/sql"
    // 引入fmt包,它是Go语言标准库中用于格式化输入输出的包
    "fmt"
    // 该驱动可在Go语言中连接和操作MySQL数据库
    "github.com/go-sql-driver/mysql"
    // 引入了Go语言的time包,用于处理时间相关的操作
    "time"
)

func main() {
    //创建一个数据库连接
    cfg := mysql.NewConfig()
    cfg.User = "****"                                                   /*用户名*/
    cfg.Passwd = "****"                                                 /*密码*/
    cfg.Net = "tcp"                                                     /*连接类型*/
    cfg.Addr = "rm-2ze1vw17v542q6b****.mysql.pre.rds.aliyuncs.com:3306" /*连接地址*/
    cfg.DBName = "****"                                                 /*数据库名称*/
    cfg.Timeout = 3 * time.Second                                       /*连接到数据库的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)*/
    cfg.ReadTimeout = 60 * time.Second                                  /*I/O读取的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)*/
    cfg.WriteTimeout = 60 * time.Second                                 /*I/O写入的超时时间,您可按需指定为毫秒(ms)、秒(s)或分钟(m)*/

    conn, err := mysql.NewConnector(cfg)
    if err != nil {
        panic(err.Error())
    }

    db := sql.OpenDB(conn)
    defer func(db *sql.DB) {
        err := db.Close()
        if err != nil {
            fmt.Printf("Error closing database connection: %v\n", err)
        }
    }(db)

    // 设置连接池参数
    db.SetMaxOpenConns(100)                 /*设置连接池中最大打开的连接数*/
    db.SetMaxIdleConns(20)                  /*设置连接池中最大空闲的连接数*/
    db.SetConnMaxIdleTime(10 * time.Minute) /*设置连接在连接池中的最长的空闲时间*/
    db.SetConnMaxLifetime(8 * time.Hour)    /*设置连接的最大生命周期*/

    // 创建一个名为 userinfo 的表
    _, err = db.Exec("create table  if not exists userinfo( uid int auto_increment, username varchar(20) not null default '', departname varchar(20) not null default '', created varchar(10) not null default '', primary key(uid) );")
    if err != nil {
        fmt.Println("create table error ", err)
        return
    }

    // 直接写入数据
    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
    res, err := stmt.Exec("James", "Research", "2016-06-17")
    id, err := res.LastInsertId()
    if err != nil {
        panic(err)
    }
    fmt.Println(id)

    // 通过绑定参数的方式写入数据
    result, err := db.Exec("INSERT INTO userinfo (username, departname, created) VALUES (?, ?, ?)", "Linda", "Testing", "2016-06-21")
    if err != nil {
        panic(err)
    }
    ids, err := result.LastInsertId()
    fmt.Println(ids)

    // 更新数据
    stmtUpdate, err := db.Prepare("UPDATE userinfo SET username=?, departname=?, created=? WHERE username=?")
    if err != nil {
        fmt.Println("Prepare update statement error:", err)
        return
    }
    resUpdate, err := stmtUpdate.Exec("Robert", "Sales", "2024-09-23", "Linda")
    if err != nil {
        fmt.Println(err)
    }
    rowCnt, _ := resUpdate.RowsAffected()
    fmt.Println(rowCnt)

    // 直接查询数据
    rows, err := db.Query("SELECT username,departname,created FROM userinfo WHERE username=?", "Robert")
    if err != nil {
        panic(err)
    }
    for rows.Next() {
        var username, departname, created string
        if err := rows.Scan(&username, &departname, &created); err != nil {
            fmt.Println(err)
        }
        fmt.Println("username:", username, "departname:", departname, "created:", created)
    }
    defer func(rows *sql.Rows) {
        err := rows.Close()
        if err != nil {
            fmt.Println(err)
        }
    }(rows)

    // 通过绑定参数的方式执行参数化查询
    stmtQuery, err := db.Prepare("SELECT username,departname,created FROM userinfo WHERE username=?")
    if err != nil {
        fmt.Println("prepare error", err)
        return
    }
    rowData, err := stmtQuery.Query("Robert")
    if err != nil {
        fmt.Println("query data error", err)
        return
    }
    for rowData.Next() {
        var username, departname, created string
        if err := rowData.Scan(&username, &departname, &created); err != nil {
            fmt.Println(err)
        }
        fmt.Println("username:", username, "departname:", departname, "created:", created)
    }
    defer func(rows *sql.Rows) {
        err := rows.Close()
        if err != nil {
            fmt.Println(err)
        }
    }(rowData)

    // 删除数据
    delStmt, _ := db.Prepare("DELETE FROM userinfo WHERE username=?")
    resultDel, err := delStmt.Exec("James")
    if err != nil {
        panic(err)
    }
    rowAffect, _ := resultDel.RowsAffected()
    fmt.Println("Data deletion completed.", rowAffect)
}

相关文档