一、如何理解数据库连接#
数据库连接池是由客户端维护的存放数据库连接的池子,连接被维护在池子里面,谁用谁来取,目的是降低频繁的创建和关闭连接的开销。
关于如何理解数据库连接,大家可以借助这个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) }