本文介绍使用Go语言的database/sql包和SQL语句在Lindorm宽表应用中进行开发的方法和示例。
前提条件
已安装GO环境,建议安装Go 1.17及以上版本。如何安装,请参见Go。
已开通MySQL协议兼容功能。如何开通,请参见开通MySQL协议兼容功能。
已将客户端IP添加至白名单,具体操作请参见设置白名单。
注意事项
Lindorm SQL前端接入节点使用SLB(负载均衡)将客户端请求分发至各个前端节点。为确保客户端请求能够更均匀地分配到所有前端节点,
设置的连接保持时间建议不宜过长,您可通过配置SetConnMaxLifetime参数来实现连接的定期刷新。
复杂网络情况下,当遇到网关性能达到瓶颈、网络链路长、网络抖动、重传率或丢包率高等情形,可能会导致连接中断。建议确保连接池配置合理,并在必要时通过业务代码侧的重试机制优化。
服务端升级重启时,连接可能会短暂中断。即使使用了连接池,业务侧仍可能感知到异常。建议捕获异常并重试。
根据业务情况合理调整连接池配置,并确保该配置生效。请确保连接池的连接数量满足业务需求,避免因连接不足而导致业务等待连接,从而引起响应时间(RT)上升。您可以通过打印连接池状态(
fmt.Printf("%+v\n", db.Stats())
)来诊断问题。
操作步骤
在Go项目的
go.mod
文件中,添加Golang MySQL Driver依赖。require github.com/go-sql-driver/mysql v1.7.1
配置连接参数。
const ( user = "user" password = "test" host = "ld-uf6k8yqb741t3****-proxy-sql-lindorm.lindorm.rds.aliyuncs.com" port = 33060 database = "default" connectTimeout = "10s" )
参数说明
参数
说明
user
如果您忘记用户密码,可以通过Lindorm宽表引擎的集群管理系统修改密码。具体操作,请参见修改用户密码。
password
host
Lindorm宽表引擎的MySQL兼容地址。如何获取,请参见查看连接地址。
重要如果应用部署在ECS实例,建议您通过专有网络访问Lindorm实例,可获得更高的安全性和更低的网络延迟。
如果应用部署在本地,在通过公网连接Lindorm实例前,需在控制台开通公网地址。开通方式:在控制台选择
,在宽表引擎页签单击开通公网地址。通过专有网络访问Lindorm实例,host请填写MySQL兼容地址对应的专有网络地址。通过公网访问Lindorm实例,host请填写MySQL兼容地址对应的公网地址。
port
Lindorm宽表引擎MySQL协议的端口,固定为33060。
database
需要连接的数据库名称。默认连接default数据库。
connectTimeout
数据库连接的超时时间。单位为秒(s)。
创建连接,通过宽表SQL语法使用Lindorm宽表引擎。以查询所有数据库为例。
url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%s", user, password, host, port, database, connectTimeout) db, err := sql.Open("mysql", url) if err != nil { panic(err.Error()) } //设置可打开连接数的最大值,默认值为0,表示不限制。根据业务实际情况设置,避免连接不够用,等待连接。 db.SetMaxOpenConns(20) //设置最大闲置连接数, 默认值为2。建议SetMaxIdleConns设置为和SetMaxOpenConns相等。 db.SetMaxIdleConns(20) // 设置连接的最大空闲时间,默认值为0,表示不超时。建议设置为 8 分钟。 db.SetConnMaxIdleTime(8 * time.Minute) // 设置连接的最大使用时间,避免长时间使用长连接,造成连接不均衡。建议设置为 30 分钟。 db.SetConnMaxLifetime(30 * time.Minute) defer db.Close() // 获取数据库中所有的database { rows, err := db.Query("show databases") if err != nil { panic(err.Error()) } defer rows.Close() for rows.Next() { var dbName string err := rows.Scan(&dbName) if err != nil { panic(err.Error()) } fmt.Println(dbName) } }
完整示例
完整示例代码如下:
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
const (
//user为Lindorm宽表引擎的用户名
user = "user"
//password为Lindorm宽表引擎的密码
password = "test"
//host为Lindorm宽表引擎的MySQL兼容地址
host = "ld-uf6k8yqb741t3****-proxy-sql-lindorm-public.lindorm.rds.aliyuncs.com"
//Lindorm宽表引擎MySQL协议的端口,固定为33060
port = 33060
//database为需要连接的数据库名称
database = "default"
//数据库连接的超时时间
connectTimeout = "20s"
)
func main() {
//打开数据库连接
url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%s", user, password, host, port, database, connectTimeout)
db, err := sql.Open("mysql", url)
if err != nil {
panic(err.Error())
}
//设置可打开连接数的最大值,默认值为0,表示不限制。根据业务实际情况设置,避免连接不够用,等待连接。
db.SetMaxOpenConns(20)
//设置最大闲置连接数, 默认值为2。建议SetMaxIdleConns设置为和SetMaxOpenConns相等。
db.SetMaxIdleConns(20)
// 设置连接的最大空闲时间,默认值为0,表示不超时。建议设置为 8 分钟。
db.SetConnMaxIdleTime(8 * time.Minute)
// 设置连接的最大使用时间,避免长时间使用长连接,造成连接不均衡。建议设置为 30 分钟。
db.SetConnMaxLifetime(30 * time.Minute)
defer db.Close()
//定期打印连接池状态, 用于诊断连接是否够用,是否存在等待连接,以及连接关闭的次数
go func() {
for {
fmt.Printf("%+v\n", db.Stats())
time.Sleep(10 * time.Second)
}
}()
//获取数据库中所有的database
{
rows, err := db.Query("show databases")
if err != nil {
panic(err.Error())
}
defer rows.Close()
for rows.Next() {
var dbName string
err := rows.Scan(&dbName)
if err != nil {
panic(err.Error())
}
fmt.Println(dbName)
}
}
//创建表
{
_, err := db.Exec("create table if not exists user_test(id int, name varchar,age int, primary key(id))")
if err != nil {
fmt.Println("create table error ", err)
return
}
}
//写入数据
//写入方法一:直接写入数据。
{
_, err = db.Exec("upsert into user_test(id,name,age) values(1,'zhangsan',17)")
if err != nil {
fmt.Println("insert data error", err)
return
}
}
//写入方法二:通过绑定参数的方式进行写入数据。
{
stmt, err := db.Prepare("upsert into user_test(id,name,age) values(?,?,?)")
if err != nil {
fmt.Println("prepare error", err)
return
}
defer stmt.Close()
_, err = stmt.Exec(2, "lisi", 18)
if err != nil {
fmt.Println("upsert error", err)
return
}
}
//查询数据
//查询方法一:直接查询数据。
{
rows, err := db.Query("select * from user_test")
if err != nil {
fmt.Println("query data error", err)
return
}
defer rows.Close()
var id int
var name string
var age int
for rows.Next() {
err = rows.Scan(&id, &name, &age)
if err != nil {
fmt.Println("scan data error", err)
return
}
fmt.Println("id:", id, "name:", name, "age:", age)
}
}
// 查询方法二:通过绑定参数的方式执行参数化查询。
{
stmt, err := db.Prepare("select * from user_test where id=?")
if err != nil {
fmt.Println("prepare error", err)
return
}
defer stmt.Close()
rows, err := stmt.Query(1)
if err != nil {
fmt.Println("query data error", err)
return
}
defer rows.Close()
var id int
var name string
var age int
for rows.Next() {
err = rows.Scan(&id, &name, &age)
if err != nil {
fmt.Println("scan data error", err)
return
}
fmt.Println("id:", id, "name:", name, "age:", age)
}
}
//删除数据
{
_, err = db.Exec("delete from user_test where id=1")
if err != nil {
fmt.Println("delete data error", err)
return
}
}
}
rows使用完成后必须调用Close关闭。
对性能要求较高的场景可以复用Stmt。
在使用QueryContext、ExecContext等与Context相关的接口时,如果设置的超时时间过短,可能因客户端垃圾回收(GC)抖动而导致Context超时进而引发连接重置,影响系统性能。
如果当前实例仅有default一个数据库,执行成功后将返回如下结果:
default
information_schema
id: 1 name: zhangsan age: 17
id: 2 name: lisi age: 18
id: 1 name: zhangsan age: 17