Golang SQL连接池梳理 (一)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 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)
}


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
SQL 关系型数据库 MySQL
mysqldiff - Golang 针对 MySQL 数据库表结构的差异 SQL 工具
Golang 针对 MySQL 数据库表结构的差异 SQL 工具。https://github.com/camry/mysqldiff
34 7
|
SQL JSON Java
知识分享之Golang——在Goland中快速基于JSON或SQL创建struct
知识分享之Golang篇是我在日常使用Golang时学习到的各种各样的知识的记录,将其整理出来以文章的形式分享给大家,来进行共同学习。欢迎大家进行持续关注。 知识分享系列目前包含Java、Golang、Linux、Docker等等。
412 0
知识分享之Golang——在Goland中快速基于JSON或SQL创建struct
|
SQL Java Linux
知识分享之Golang——使用gorm时进行执行自定义SQL的几种方式
知识分享之Golang篇是我在日常使用Golang时学习到的各种各样的知识的记录,将其整理出来以文章的形式分享给大家,来进行共同学习。欢迎大家进行持续关注。 知识分享系列目前包含Java、Golang、Linux、Docker等等。
496 0
知识分享之Golang——使用gorm时进行执行自定义SQL的几种方式
|
SQL 网络协议 安全
Golang SQL连接池梳理 (三)
Golang SQL连接池梳理 (三)
230 0
|
SQL 缓存 数据库连接
Golang SQL连接池梳理 (二)
Golang SQL连接池梳理 (二)
182 0
|
5天前
|
SQL DataWorks NoSQL
DataWorks产品使用合集之如何将SQL Server中的数据转存到MongoDB
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
29天前
|
SQL API 流计算
实时计算 Flink版产品使用合集之在Mac M1下的Docker环境中开启SQL Server代理的操作步骤是什么
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
156 1
|
23天前
|
SQL 存储 搜索推荐
SQL server增删改查(1)
SQL server增删改查(1)
106 0
|
24天前
|
SQL 关系型数据库 数据库
阿里云数据库 RDS SQL Server版实战【性能优化实践、优点探析】
本文探讨了Amazon RDS SQL Server版在云数据库中的优势,包括高可用性、可扩展性、管理便捷、安全性和成本效益。通过多可用区部署和自动备份,RDS确保数据安全和持久性,并支持自动扩展以适应流量波动。可视化管理界面简化了监控和操作,而数据加密和访问控制等功能保障了安全性。此外,弹性计费模式降低了运维成本。实战应用显示,RDS SQL Server版能有效助力企业在促销高峰期稳定系统并保障数据安全。阿里云的RDS SQL Server版还提供了弹性伸缩、自动备份恢复、安全性和高可用性功能,进一步优化性能和成本控制,并与AWS生态系统无缝集成,支持多种开发语言和框架。
169 2
|
24天前
|
SQL JSON atlas
实时计算 Flink版产品使用合集之SQL Server CDC是否支持抽取SQL Server视图
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。