在Golang中如何正确地使用database/sql包访问数据库
本文记录了我在实际工作中关于数据库操作上一些小经验,也是新手入门golang时我认为一定会碰到问题,没有什么高大上的东西,所以希望能抛砖引玉,也算是对这个问题的一次总结。
其实我也是一个新手,机缘巧合几个月前开始做golang开发,以前一直是以.NET技术栈为主,文章如有错误不吝指正。
访问数据库
相信大家第一次碰到这个问题的时候应该和我一样,去网上找个例子参考一下。没错,这样的例子一搜一大把,于是我们很容易(抄)写了如下一段代码:
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql","root:111111@tcp(127.0.0.1:3306)/testdb")
if err != nil {
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Successfully connected!")
}
把程序运行起来一看,成功地输出了想看到的东西,内心一阵暗喜“so easy"。于是把这段代码封装成一个公共方法供其他地方调用:
func GetDbContext() *sql.DB {
db, err := sql.Open("mysql","root:111111@tcp(127.0.0.1:3306)/testdb")
if err != nil {
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
return db
}
func DoSomething(){
db := GetDbContext()
rows,_ := db.Query("select * from table1")
}
没错我最早就是这么干的,然后开始愉快地转头写CRUD了,不过事情可没这么简单。
很快, 编码五分钟捉虫两小时开场了。
慢慢的我就发现,在连续多次操作数据库后就偶尔发生程序卡死的情况,请求一直是pending状态,只能杀死进程重启才可以。刚开始没在意,也没有怀疑是数据库操作有问题,但后来越来越频繁严重影响到程序开发,没办法就记log加断点调试看是哪里出了问题。经过反复验证后确定问题就出在执行SQL语句这里,这下懵了,我看网上大家都是这么写的怎么会有问题??
连接池问题
根据多年开发经验,大胆猜测SQL执行失败最大的可能性就是数据库连接不上,在确认数据库没有崩掉的情况下开始研究代码哪里写的不对,但是前后也就那么几行代码实在看不出什么毛病,只能开始深入了研究database/sql包的知识点。
通过查资料发现open完数据库后的返回对象sql.DB
实际上是一个连接池对象,并不是单纯的某一个连接。它是一个抽象的数据访问接口,和数据库类型无关,当然也就和具体的数据库Schema无关。我们要实现某一个数据库的访问单纯用这个包是不够的,还要引入具体的数据库驱动包,这个驱动才是真正实现数据库访问的东西。
现在再回过头来看代码,既然open创建了连接池,那用完把它销毁不就好了,于是参考官网文档稍加改进:
func GetDbContext() *sql.DB {
db, err := sql.Open("mysql","root:111111@tcp(127.0.0.1:3306)/testdb")
if err != nil {
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
return db
}
func DoSomething(){
db := GetDbContext()
defer db.Close()
rows,_ := db.Query("select * from table1")
}
看似行得通,但是估计没人愿意这样做。原因很明显,别的先不谈,创建和销毁连接池开销太大了,你这样对它于心何忍,拿着屠龙刀去砍柴。
使用连接池的好处就是不需要开发者频繁地创建和销毁连接,这两项工作都交给了连接池去做,我们只需要在使用前找它要一个可用的连接,用完还回去就可以了。
这里引用一段官方文档中的原话:
Although it’s idiomatic to Close() the database when you’re finished with it, the sql.DB object is designed to be long-lived. Don’t Open() and Close() databases frequently. Instead, create one sql.DB object for each distinct datastore you need to access, and keep it until the program is done accessing that datastore. Pass it around as needed, or make it available somehow globally, but keep it open. And don’t Open() and Close() from a short-lived function. Instead, pass the sql.DB into that short-lived function as an argument.
核心意思就是sql.DB
是一个长生命周期对象,你不要随便打开和关闭,并且建议你在程序中为每一个数据库创建唯一的sql.DB
。
那么现在的问题就是如何保证程序中只有一个连接池呢?
很简单,使用一个全局变量即可,有点类似C#和java中static的味道,在Golang中可以使用如下方法声明一个全局对象:
package demo
import (
"database/sql"
)
var mydb,_ = sql.Open("mysql","connection_string")
不过我们的业务场景比较特殊,系统中有很多个数据库,要根据不同参数去连不同数据库,那么上面这种声明赋值方式就不行了,我稍加改进,结合map实现了连接池动态管理:
var envdbMap map[string]*sql.DB
func GetEnvDbContext(connector config.DbConnector) *sql.DB {
if envdbMap == nil {
envdbMap = make(map[string]*sql.DB)
}
db, ok := envdbMap[connector.ID]
if ok {
return db
} else {
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", connector.Host, connector.Port, connector.UserName, connector.Password, connector.DatabaseName)
db, err := sql.Open("postgres", connStr)
envdbMap[connector.ID] = db
return db
}
}
原理很简单,就是用map装池子,池子装连接。
有借有还
到这里连接池已经准备好了,那么如何从池子中取一个可用的连接呢?这点池子已经帮大家考虑的很周到了,大家不需要写额外代码去获取连接,直接拿起池子用就可以了,内部会有一系列机制帮你弄到一个连接去执行SQL,以后有机会对池子的原理来做个解析。
但是用完要记得还回去,这个必须你手动去做,例如:
rows,_ := db.Query("select * from table1")
defer rows.Close()
// do sth...
最好不要在do sth
之后做Close,因为一旦你这个过程中发生异常,导致后面的Close无法执行,那么这个连接就一直被占用,日积月累TCP连接就被你耗光了。
官方文档说了,rows.Close()
是一种无害(harmless)操作,你可以做多次,但是不能忘了做。
这里有个特殊情况要注意,对于那种没有返回结果的SQL语句,千万不要使用Query方法去执行,这会导致无法回收连接,这时候推荐使用Exec方法去执行。
配置连接池
默认情况下连接池没有数量限制,但是我们的机器有TCP的数量限制,不要因为一个程序拖死一台机器,所以不推荐无限量的去使用。database/sql包提供了几个连接池配置参数,主要包含:
- db.SetMaxIdleConns(N) 设置空闲连接的数量
- db.SetMaxOpenConns(N) 设置打开的连接数量
- db.SetConnMaxLifetime(duration) 设置连接的生存时间
详细的介绍大家可以参考官方文档。
总结
经过以上分析,可以清晰的知道最开始的bug就是因为错误地使用了连接池导致数据库连接被耗光从而无法执行SQL语句,其实说简单也很简单。
以上就是工作中使用golang访问数据库的踩坑历程,希望能帮到新接触golang的朋友,如有错误的地方欢迎指出,以免误导他人。
参考连接
http://go-database-sql.org/accessing.html
http://go-database-sql.org/retrieving.html
http://go-database-sql.org/connection-pool.html
在Golang中如何正确地使用database/sql包访问数据库的更多相关文章
- Golang中如何正确的使用sarama包操作Kafka?
Golang中如何正确的使用sarama包操作Kafka? 一.背景 在一些业务系统中,模块之间通过引入Kafka解藕,拿IM举例(图来源): 用户A给B发送消息,msg_gateway收到消息后,投 ...
- golang——database/sql包学习
1.database/sql包 sql包提供了保证SQL或类SQL数据库的泛用接口. 使用sql包时必须注入(至少)一个数据库驱动. (1)获取mysql driver:go get -v githu ...
- 使用Spring.net中对Ado.net的抽象封装来访问数据库
使用Spring.net中对Ado.net的抽象封装来访问数据库 Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序.它提供了很多方面的功能,比如依赖注入 ...
- 关于Golang中database/sql包的学习
go-sql-driver 请求一个连接的函数有好几种,执行完毕处理连接的方式稍有差别,大致如下: db.Ping() 调用完毕后会马上把连接返回给连接池. db.Exec() 调用完毕后会马上把连接 ...
- Golang中database/sql包
驱动 github.com/go-sql-driver/mysql 请求一个连接的函数有好几种,执行完毕处理连接的方式稍有差别,大致如下: db.Ping() 调用完毕后会马上把连接返回给连接池. d ...
- Golang中Struct与DB中表字段通过反射自动映射 - sqlmapper
Golang中操作数据库已经有现成的库"database/sql"可以用,但是"database/sql"只提供了最基础的操作接口: 对数据库中一张表的增删改查 ...
- go标准库的学习-database/sql
参考:https://studygolang.com/pkgdoc 导入方式: import "database/sql" sql包提供了保证SQL或类SQL数据库的泛用接口. 使 ...
- go database/sql sql-driver/mysql 操作
这里使用的是github.com/Go-SQL-Driver/MySQL, 所以需要下载一个github.com/Go-SQL-Driver/MySQL 引入 database/sql 和 githu ...
- golang中Context的使用场景
golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...
随机推荐
- net core cap结合redis+数据库实现最终一致性
CAP 同时支持使用 RabbitMQ,Kafka,Azure Service Bus 等进行底层之间的消息发送. CAP 目前支持使用 Sql Server,MySql,PostgreSql,Mon ...
- Autofac的基本使用---2、普通类型
Autofac的基本使用---目录 准备 使用的表是Student,创建相关的IDAL.DAL.IBLL.BLL层. 使用EF,创建一个Model层,存放edmx文件. 控制台程序的使用 using ...
- HttpApplication执行顺序
类的实例(Global继承自该类)是在 ASP.NET 基础结构中创建的,而不是由用户直接创建的.HttpApplication 类的一个实例在其生存期内被用于处理多个请求,但它一次只能处理一个请求. ...
- 简单了解一下 Nginx
一.Nginx 基本认识 1.Nginx 是什么? Nginx 是一款开源的.轻量级的.高性能的 HTTP 服务器 以及 反向代理服务器. 特点是 占有内存少.并发能力强. 2.Nginx 用来干什么 ...
- ES6语法:class类,从了解到使用
前期提要: JavaScript 语言中,在使用类之前,生成实例对象的传统方法是通过使用构造函数. 一.构造函数: 定义:通过 new 函数名 来实例化对象的函数叫构造函数. 主要功能:为初 ...
- intellij idea svn不能更新和提交
进入设置–version control – subversion如下图,将前边的选项的勾全部去掉,点击ok
- [永恒之黑]CVE-2020-0796(漏洞复现)
实验环境: 靶机:windows10 1903 专业版 攻击机:kali 2020.3 VMware:vmware14 实验工具: Python 3.8.5 msfconsole 实验PROC: ...
- 使用freetype来显示中文汉字和英文字符
这里我们用到了freetype.进入官网http://savannah.nongnu.org/download/freetype/ 中下载最新的版本2.7的源代码和文件.freetype-2.7.ta ...
- 买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 两笔 交易. 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的 ...
- i5 11300h和R5 5600H 的区别 哪个好
酷睿i5-11300H配置为4个内核及8个线程,具备8MB的L3缓存和5MB的L2缓存,基础频率3.10GHz最高睿频4.40GHz.Intel的显卡将集成Xe GPU内核.至于TDP,i5-1130 ...