Golang SQL连接池梳理 (一)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Golang SQL连接池梳理 (一)

一、如何理解数据库连接#


数据库连接池是由客户端维护的存放数据库连接的池子,连接被维护在池子里面,谁用谁来取,目的是降低频繁的创建和关闭连接的开销。


关于如何理解数据库连接,大家可以借助这个TCP编程的Demo来理解。


为了便于理解,可以MySQL-Server的连接池想象成就是这个简单的Tcp-Server


func main() {
  // 1. 监听端口 2.accept连接 3.开goroutine处理连接
  listen, err := net.Listen("tcp", "0.0.0.0:9090")
  if err != nil {
    fmt.Printf("error : %v", err)
    return
  }
  for{
    conn, err := listen.Accept()
    if err != nil {
      fmt.Printf("Fail listen.Accept : %v", err)
      continue
    }
    go ProcessConn(conn)
  }
}
// 处理网络请求
func ProcessConn(conn net.Conn) {
  // defer conn.Close()
  for  {
    bt,err:= coder.Decode(conn)
    if err != nil {
      fmt.Printf("Fail to decode error [%v]", err)
      return
    }
    s := string(bt)
    fmt.Printf("Read from conn:[%v]\n",s)
  }
}


对于我们现在看的sql包下的连接池,可以简化认为它就是如下的tcp-client


conn, err := net.Dial("tcp", ":9090")
  defer conn.Close()
  if err != nil {
    fmt.Printf("error : %v", err)
    return
  }
  // 将数据编码并发送出去
  coder.Encode(conn,"hi server i am here");
  time.Sleep(time.Second*10


总体的思路可以认为,程序启动的时候,根据我们的配置,sql包中的DB会为我们提前创建几条这样的conn,然后维护起来,不close()掉,我们想使用的时候问他拿即可。


至于为什么是这个tcp的demo呢?因为数据库连接的建立底层依赖的是tcp连接。基于tcp连接的基础上实现客户端和服务端数据的传输,再往上封装一层mysql的握手、鉴权、交互协议对数据包进行解析、反解析,进而跑通整个流程。


二、连接池的工作原理#


  • 连接池的建立
  • 后台系统初始化时,连接池会根据系统的配置建立。
  • 但是在接受客户端请求之前,并没有真正的创建连接。
  • 在go语言中,先注册驱动_ "github.com/go-sql-driver/mysql"
  • 初始化DB,调用Open函数,这时其实没有真正的去获取连接,而是去获取DB操作的数据结构。
  • 连接池中连接的使用和管理
  • 连接池的关闭
  • 释放连接
  • 关闭连接的请求队列
  • connectionOpener(负责打开连接的协程)
  • connectionResetter(重制连接状态的协程)
  • connectionCleaner(定期清理过期连接的协程)


三、database/sql包结构#



driver/driver.go :定义了实现数据库驱动所需要的接口,这些接口由sql包和具体的驱动包来实现


driver/types.go:定义了数据类型别名和转换


convert:rows的scan


sql.go: 关于SQL数据库的一些通用的接口、类型。包括:连接池、数据类型、连接、事物、statement


import "github.com/go-sql-driver/mysql” // 具体的驱动包
import "database/sql"
// 初始化连接
func initDB() (err error) {
  db, err = sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
  if err != nil {
    panic(err)
  }
  // todo 不要在这里关闭它, 函数一结束,defer就执行了
  // defer db.Close()
  err = db.Ping()
  if err != nil {
    return err
  }
  return nil
}


四、三个重要的结构体#


4.1、DB#


/**
  DB是代表零个或多个基础连接池的数据库句柄。 对于多个goroutine并发使用是安全的。
  sql包会自动创建并释放连接。 它还维护空闲连接的空闲池。 
  如果数据库具有每个连接状态的概念,则可以在事务(Tx)或连接(Conn)中可靠地观察到这种状态。
  调用DB.Begin之后,返回的Tx将绑定到单个连接。 
  在事务上调用Commit或Rollback后,该事务的连接将返回到DB的空闲连接池。
  池大小可以通过SetMaxIdleConns控制。
*/
type DB struct {
  // Atomic access only. At top of struct to prevent mis-alignment
  // on 32-bit platforms. Of type time.Duration.
  // 统计使用:等待新的连接所需要的总时间
  waitDuration int64 // Total time waited for new connections.
  // 由具体的数据库驱动实现的 connector
  connector driver.Connector
  // numClosed is an atomic counter which represents a total number of
  // closed connections. Stmt.openStmt checks it before cleaning closed
  // connections in Stmt.css.
  // 关闭的连接数
  numClosed uint64
  mu           sync.Mutex // protects following fields
  // 连接池,在go中,连接的封装结构体是:driverConn
  freeConn     []*driverConn
  // 连接请求的map, key是自增的int64类型的数,用于唯一标示这个请求分配的
  connRequests map[uint64]chan connRequest
  // 类似于binlog中的next trx_ix ,下一个事物的id
  nextRequest  uint64 // Next key to use in connRequests.
  // 已经打开,或者等待打开的连接数
  numOpen      int    // number of opened and pending open connections
  // Used to signal the need for new connections
  // a goroutine running connectionOpener() reads on this chan and
  // maybeOpenNewConnections sends on the chan (one send per needed connection)
  // It is closed during db.Close(). The close tells the connectionOpener
  // goroutine to exit.
  // 他是个chan,用于通知connectionOpener()协程应该打开新的连接了。
  openerCh          chan struct{}
  // 他是个chan,用于通知connectionResetter协程:重制连接的状态。
  resetterCh        chan *driverConn
  closed            bool
  // 依赖,key是连接、statement
  dep               map[finalCloser]depSet
  lastPut           map[*driverConn]string // stacktrace of last conn's put; debug only
  // 连接池的大小,0意味着使用默认的大小2, 小于0表示不使用连接池
  maxIdle           int    // zero means defaultMaxIdleConns; negative means 0
  // 最大打开的连接数,包含连接池中的连接和连接池之外的空闲连接, 0表示不做限制
  maxOpen           int    // <= 0 means unlimited
  // 连接被重用的时间,设置为0表示一直可以被重用。
  maxLifetime       time.Duration  // maximum amount of time a connection may be reused
  // 他是个chan,用于通知connectionCleaner协程去请求过期的连接
  // 当有设置最大存活时间时才会生效
  cleanerCh         chan struct{}
  // 等待的连接总数,当maxIdle为0时,waitCount也会一直为
  // 因为maxIdle为0,每一个请求过来都会打开一条新的连接。
  waitCount         int64 // Total number of connections waited for.
  // 释放连接时,因为连接池已满而关闭的连接总数
  // 如果maxLifeTime没有被设置,maxIdleClosed为0
  maxIdleClosed     int64 // Total number of connections closed due to idle.
  // 因为超过了最大连接时间,而被关闭的连接总数
  maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
  // 当DB被关闭时,关闭connection opener和session resetter这两个协程
  stop func() // stop cancels the connection opener and the session resetter.
}


4.2、driverConn#


连接的封装结构体:driverConn


// driverConn wraps a driver.Conn with a mutex, to
// be held during all calls into the Conn. (including any calls onto
// interfaces returned via that Conn, such as calls on Tx, Stmt,
// Result, Rows)
/**
  driverConn使用互斥锁包装Conn包装
*/
type driverConn struct {
  // 持有对整个数据库的抽象结构体
  db        *DB       
  createdAt time.Time 
  sync.Mutex  // guards following
  // 对应于具体的连接,eg.mysqlConn
  ci          driver.Conn
  // 标记当前连接的状态:当前连接是否已经关闭
  closed      bool
  // 标记当前连接的状态:当前连接是否最终关闭,包装 ci.Close has been called
  finalClosed bool // ci.Close has been called
  // 在这些连接上打开的statement
  openStmt    map[*driverStmt]bool
  // connectionResetter返回的结果
  lastErr     error // lastError captures the result of the session resetter.
  // guarded by db.mu
  // 连接是否被占用了
  inUse      bool
  // 在归还连接时需要运行的代码。在noteUnusedDriverStatement中添加
  onPut      []func() // code (with db.mu held) run when conn is next returned
  dbmuClosed bool     // same as closed, but guarded by db.mu, for removeClosedStmtLocked
}


4.3、Conn#


具体的连接: driver包下的Conn如下,是个接口,需要被具体的实现。


// Conn is assumed to be stateful.
type Conn interface {
  // Prepare returns a prepared statement, bound to this connection.
  Prepare(query string) (Stmt, error)
  // Close invalidates and potentially stops any current
  // prepared statements and transactions, marking this
  // connection as no longer in use.
  //
  // Because the sql package maintains a free pool of
  // connections and only calls Close when there's a surplus of
  // idle connections, it shouldn't be necessary for drivers to
  // do their own connection caching.
  Close() error
  // Begin starts and returns a new transaction.
  //
  // Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
  Begin() (Tx, error)
}


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6月前
|
SQL 关系型数据库 MySQL
mysqldiff - Golang 针对 MySQL 数据库表结构的差异 SQL 工具
Golang 针对 MySQL 数据库表结构的差异 SQL 工具。https://github.com/camry/mysqldiff
96 7
|
SQL JSON Java
知识分享之Golang——在Goland中快速基于JSON或SQL创建struct
知识分享之Golang篇是我在日常使用Golang时学习到的各种各样的知识的记录,将其整理出来以文章的形式分享给大家,来进行共同学习。欢迎大家进行持续关注。 知识分享系列目前包含Java、Golang、Linux、Docker等等。
480 0
知识分享之Golang——在Goland中快速基于JSON或SQL创建struct
|
SQL Java Linux
知识分享之Golang——使用gorm时进行执行自定义SQL的几种方式
知识分享之Golang篇是我在日常使用Golang时学习到的各种各样的知识的记录,将其整理出来以文章的形式分享给大家,来进行共同学习。欢迎大家进行持续关注。 知识分享系列目前包含Java、Golang、Linux、Docker等等。
577 0
知识分享之Golang——使用gorm时进行执行自定义SQL的几种方式
|
SQL 缓存 数据库连接
Golang SQL连接池梳理 (二)
Golang SQL连接池梳理 (二)
212 0
|
SQL 网络协议 安全
Golang SQL连接池梳理 (三)
Golang SQL连接池梳理 (三)
262 0
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
129 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
71 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
110 3
Golang语言之gRPC程序设计示例
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
100 4
|
3月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
69 4
Golang语言goroutine协程篇