一、什么是连接? 什么是连接池?
-
mysql连接
如果你要使用程序进行一次mysql查询,mysql就会返回一个连接。或者你使用命令行mysql -u root -p123456
主动发起一个mysql连接。 -
连接池
如果你使用完之后,希望这个连接不要断,把这个连接存起来,那么这些存起来的连接集合就是连接池
查询当前的连接mysqladmin -uroot -p123456 processlist
,processlist指Show list of active threads in server。
二、连接的类型
通过MySQL 客户端连接db的是交互会话,通过jdbc等程序连接db的是非交互会话。
三、连接的状态
- open
当有连接处于任务期间(如执行查询、更新等操作)时,该连接即为open状态。SetMaxOpenConns可以配置允许同时处于open状态的连接数。 - idle
当连接完成任务后即处于空闲状态。SetMaxIdleConns可以配置允许空闲的数量。
如果连接池中你允许的空闲数量为0,即每个连接都将是短连接。在完成任务后自动释放。
如果连接池中你允许的空闲数量大于0,当连接完成任务后将继续存在连接池中。等下一次有任务过来时,将会返回连接池中的空闲连接去执行任务。
四、连接超时
-
wait_timeout
MySQL服务器关闭非交互式连接前等待的秒数。
当连接保持空闲状态超过一定时间没有任务处理,则该空闲连接将会过期。mysql默认是8h。
使用root
账号执行以下命令,并新开一个连接,连接的超时不受WAIT_TIMEOUT限制。因为这里是交互式会话连接所以不受影响。set global WAIT_TIMEOUT=3;
但如果执行下面的命令,则立刻会对该会话的当前连接生效。但不作用于新返回的连接,原因不知。
set session WAIT_TIMEOUT=3;
-
MaxLifetime
生命时长超时。指从连接创建开始到当前时间。 -
interactive_timeout
MySQL服务器关闭交互式连接前等待的秒数。即交互式连接超时。当使用命令行进行连接时,从获得连接开始,默认8h。
执行以下命令,并打开一个新连接,超过时间限制后进行访问,将会提示MySQL server has gone awayset global INTERACTIVE_TIMEOUT=10;
-
ReadTimeout
在mysql命令行,从发送请求开始,超过一定时间仍未返回数据。主要和网络环境相关。
以上几个超时时间,有几个点需要注意
- wait_timeout是非交互式的超时时间,interactive_timeout是交互式的超时时间
- 服务端wait_timeout默认的超时时长是8h,如果允许连接池出现空闲连接,且code端不限制超时,则会出现连接超时不可用的异常。需在code端限制连接时间小于wait_timeout。即SetConnMaxLifetime<wait_timeout。
下面使用gorm进行测试WAIT_TIMEOUT
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type People struct {
Age int
}
func main() {
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Printf("%v", err)
return
}
people := &People{}
err = db.Table("people").First(people).Error
fmt.Printf("%v %v", people, err)
}
- 执行
set global WAIT_TIMEOUT=3;
- code执行到获取连接’db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})’,
- 执行
mysqladmin -uroot -p123456 processlist
,可看见有code的db连接。
- 3秒后再执行
mysqladmin -uroot -p123456 processlist
,可以看见那个连接失效了。 - 执行完剩余代码,没有err,但有以下提示。原来了的空闲连接失效了,最后重新返回了一个新的连接,并返回了数据。执行完查询语句时,可以通过命令查询当前连接,的确是出现了新的db连接。
四、Golang 访问mysql
在database/sql 下,sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作,sql.DB 为我们管理数据库连接池。通过sql.DB获取到的连接,默认是一个长连接
。
需要注意的是,sql.DB
表示操作数据库的抽象访问接口
,而非一个数据库连接对象。它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。
需要注意的点:
- sql.Open并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;
- sql.DB表示操作数据库的
抽象接口的对象
,但不是所谓的数据库连接对象,sql.DB对象只有当需要使用时才会创建连接
,如果想立即验证连接,需要用Ping()方法; - sql.Open返回的sql.DB对象是
协程并发安全
的. - sql.DB的设计就是用来作为
长连接
使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。
连接配置方式:
DB.SetMaxOpenConns(1)
,设置允许open的连接数量,如果设置为0,认为是1。DB.SetMaxIdleConns(1)
,设置允许空闲的连接,如果设置为0,则认为全为短连接,使用完后自动释放。
package main
import (
"database/sql"
"errors"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
_ = testDB()
}
func getDB() *sql.DB {
var DBTemp *sql.DB
var dataBase = "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True"
var err error
DBTemp, err = sql.Open("mysql", dataBase)
if err != nil {
log.Fatalln("open db fail:", err)
}
DBTemp.SetMaxOpenConns(1)
DBTemp.SetMaxIdleConns(1)
return DBTemp
}
func testDB() error {
DB := getDB()
defer DB.Close()
rows, err := DB.Query("select * from people")
if err != nil {
return err
}
defer rows.Close()
//字典类型
//构造scanArgs、values两个数组,scanArgs的每个值指向values相应值的地址
columns, _ := rows.Columns()
scanArgs := make([]interface{}, len(columns))
values := make([]sql.RawBytes, len(columns))
for i := range values {
scanArgs[i] = &values[i]
}
//最后得到的map
results := make(map[int]map[string]string)
i := 0
for rows.Next() {
//将行数据保存到record字典
err = rows.Scan(scanArgs...)
if err != nil {
return errors.New("结果组装失败,原因:\n" + err.Error())
}
row := make(map[string]string)
for k, v := range values {
key := columns[k]
row[key] = string(v)
}
results[i] = row
i++
}
return nil
}