经常在群里看的有小伙伴问, 使用 Go 语言开发的时候经常会碰到数据的一些报错比如下面这位朋友:
我记得我刚开始写 Go 的时候也遇到过, 那么今天就来讲讲如何正确的使用 Mysql Driver 吧!
官方文档 https://golang.org/pkg/database/sql/#DB , 虽然写了如何使用它来执行 SQL 数据库查询和语句的例子, 但是没有很好的说明如何正确的配置 sql.DB 改善性能, SetMaxOpenConns(), SetMaxIdleConns() and SetConnMaxLifetime()这三个方法也通常被初学者忽略.
Open and idle connections:
一个 sql.DB 是包含许多'open'和'idle'连接的数据库连接池的对象. 当你使用它执行数据库任务时(例如执行 SQL 语句或查询行), 连接标记为打开. 任务完成后, 连接变为空闲状态.
当你使用 sql.DB 执行数据库任务时, 它将首先检查池中是否有空闲连接. 如果有一个可用, 那么 Go 将重新使用现有连接并在任务持续期间将其标记为打开. 如果游泳池中没有空闲的连接, 那么 Go 会创建一个新的连接并 "打开" 它.
MySQL 短连接每次请求操作数据库都需要建立与 MySQL 服务器建立 TCP 连接, 这是需要时间开销的. TCP 连接需要 3 次网络通信. 这样就增加了一定的延时和额外的 IO 消耗. 请求结束后会关闭 MySQL 连接, 还会发生 3/4 次网络通信. close 操作不会增加响应延时, 原因是 close 后是由操作系统自动进行通信的, 应用程序感知不到, 长连接就可以避免每次请求都创建连接的开销, 节省了时间和 IO 消耗.
- type Config struct {
- DSN string
- Active int // pool
- Idle int // pool
- IdleTimeout xtime.Duration // connect max life time.
- }
- // NewMysql initialize mysql connection .
- func NewMysql(c *Config) (db *sql.DB ) {
- // TODO add query exec and transaction timeout .
- db, err := Open(c)
- if err != nil {
- panic(err)
- }
- return
- }
- func Open(c *Config) (db *sql.DB, err error ) {
- db, err = sql.Open("mysql", c.DSN)
- if err != nil {
- log.Error("sql.Open() error(%v)", err)
- return nil, err
- }
- db.SetMaxOpenConns(c.Active)
- db.SetMaxIdleConns(c.Idle) // 默认情况下, sql.DB 允许在连接池中保留最多 2 个空闲连接
- db.SetConnMaxLifetime(time.Duration(c.IdleTimeout ))
- return db, nil
- }
从理论上讲, 在连接池中允许更多的空闲连接会提高性能, 因为它不太可能需要从头开始建立新的连接, 因此有助于节省资源.
那么我们应该保持一个大的空闲连接池?
其实不然, 保持空闲连接是有代价的, 它需要占用的内存的, 这点需要注意. 设置多大的 Idle 应该根据自身应用程序来定. 如果连接闲置时间过长, 则可能无法使用. 例如, MySQL 的 wait_timeout 设置会自动关闭任何 8 小时未使用的连接 (默认情况下), 所以我们看到上面的代码我们设置了自己的超时时间. 发生这种情况时, sql.DB 会优雅地的关掉它. 在关闭之前, 连接会自动重试两次, 此时 Go 将从池中移除连接并创建一个新连接. 因此, 如果将 MaxIdleConns 设置得太高, 实际上可能会导致连接变得无法使用, 并且比使用更少空闲连接池(使用更频繁的连接数更少) 时使用的资源更多.
总结:
对于大多数使用 SetMaxOpenConns()来限制打开连接的最大数量的程序, 都会对性能产生负面影响, 但如果数据库资源比较紧张的情况下, 这么做还是有好处的.
如果程序突发或定期同时执行两个以上的数据库任务, 那么通过 SetMaxIdleConns()增加空闲连接池的大小可能会产生积极的性能影响. 但是需要注意的是设置过大可能会适得其反. 上线之前最好做个压测已到达最佳性能.
对于大多数通过 SetConnMaxLifetime()设置连接超时的应用程序, 都会对性能产生负面影响. 但是, 如果你的数据库本身强制实现一个短的连接生命周期, 那么在 sql.DB 对它进行设置是有价值的, 以避免尝试和重试错误连接的开销.
如果希望程序在数据库达到硬连接限制时等待连接释放 (而不是返回错误), 则应该明确设置 SetMaxOpenConns() 和 SetMaxIdleConns().
Demo:
mysql.go
对 github.com/go-sql-driver/mysql 进行简单的封装 可以通过配置文件初始化 Config.
- package mysql
- import (
- "database/sql"
- "time"
- _ "github.com/go-sql-driver/mysql"
- )
- // Config is mysql config.
- type Config struct {
- DSN string
- Active int // pool
- Idle int // pool
- IdleTimeout time.Time // connect max life time.
- }
- // NewMysql initialize mysql connection .
- func NewMysql(c *Config) (db *sql.DB ) {
- // TODO add query exec and transaction timeout .
- db, err := Open(c)
- if err != nil {
- panic(err)
- }
- return
- }
- func Open(c *Config) (db *sql.DB, err error ) {
- db, err = sql.Open("mysql", c.DSN)
- if err != nil {
- log.Error("sql.Open() error(%v)", err)
- return nil, err
- }
- db.SetMaxOpenConns(c.Active)
- db.SetMaxIdleConns(c.Idle)
- db.SetConnMaxLifetime(time.Hour)
- return db, nil
- }
- dao.go
初始化 sql.db
- import (
- "database/sql"
- "xxx/xxx/conf"
- "xxx/xx/mysql"
- )
- type Dao struct {
- c *conf.Config
- db *sql.DB
- }
- // New initial mysql dao .
- func New(c *conf.Config) (d *Dao ) {
- d = &Dao{
- c: c,
- db: mysql.NewMysql(c.Mysql.Master),
- }
- return
- }
- // Ping check mysql health.
- func (d *Dao) Ping () (err error) {
- return d.db.Ping()
- }
- // Close release all mysql resource .
- func (d *Dao) Close () {
- d.db.Close()
- }
- // BeginTran begin mysql transaction .
- func (d *Dao) BeginTran () (*sql.Tx, error) {
- return d.db.Begin()
- }
- xxx.go
- const (
- _inSQL = "INSERT INTO xxx (mobile,password) VALUES (?,?)"
- )
- // InsertUser insert user .
- func (d *Dao) InsertUser (mobile, password string) (affect int64, err error) {
- var res sql.Result
- if res, err = d.db.Exec(_inSQL, mobile, password); err != nil {
- log.Error("db.Exec error(%v)", err)
- return
- }
- return res.RowsAffected()
- }
来源: https://juejin.im/entry/5ade9d18f265da0b7b358915