前言
后端程序主要是数据驱动型的,因此大多数后端应用也就主要涉及几类和数据相关的操作:读数据,写数据,处理数据。其中,读写是处理的基础。读写操作必然涉及编程语言和各种各样数据库的交互,因此任何后端编程语言都必须提供良好的数据库交互接口方便程序读写数据。本文主要是介绍golang的数据库操作原理。
代码框架
现在生产中使用的数据库多种多样,因此在语言层面实现所有数据库的交互必然会导致编程语言变得无比臃肿。为了避免这个问题,golang通过接口的形式引入数据库驱动,使得我们可以从用户的角度自己编写数据库驱动,并在使用时嵌入到golang中。用go的机制来说,golang只需要提供一个Driver的interface,用户根据自己的数据库编写驱动实现Driver即可。当然,很多时候go官方已经以库的形式提供了常用的数据库操作驱动,我们只需要import即可。
通过以上对go操作数据库的简单阐述,我们大概可以知道知道它具有热插拔,灵活性等优点。为了深入阐述它的实现原理,我们必须对以下问题作出回答:
- Driver是如何嵌入到go中的?
- go是如何实现连接池管理的?
这里先回答第一个问题。
go语言为所有类型的数据库提供了一个统一的结构体:
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 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
freeConn []*driverConn
connRequests map[uint64]chan connRequest
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.
openerCh chan struct{}
resetterCh chan *driverConn
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdle int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
maxIdleClosed int64 // Total number of connections closed due to idle.
maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
stop func() // stop cancels the connection opener and the session resetter.
}
从这个DB结构体可以看到,它主要是包含连接池的控制信息。因为说白了,从客户端的角度来看,不同的sql数据库之间之所以不同主要是因为其指令不同,从C/S的角度来看的话,也就是网络传输格式的不同。所以说Driver本质上是实现不同的sql数据库中客户端与服务端的数据传输格式。所以我们可以看到,go语言自己实现不同数据库的连接池管理(因为这具有普遍性),而各个Driver则简单地只负责按照不同数据库通信要求,实现客户端到服务端的通信编码。因此Driver的实现是比较轻量级的。
DB中的connector 成员就是Driver的插口:
type Connector interface {
// Connect returns a connection to the database.
// Connect may return a cached connection (one previously
// closed), but doing so is unnecessary; the sql package
// maintains a pool of idle connections for efficient re-use.
//
// The provided context.Context is for dialing purposes only
// (see net.DialContext) and should not be stored or used for
// other purposes.
//
// The returned connection is only used by one goroutine at a
// time.
Connect(context.Context) (Conn, error)
// Driver returns the underlying Driver of the Connector,
// mainly to maintain compatibility with the Driver method
// on sql.DB.
Driver() Driver
}
它是一个interface类型。其中的connect函数就是connector和Driver的连接桥梁。一般connector都是一个包含Driver的结构体,该connector的connect函数通过调用Driver的Open函数获得连接,放入到DB结构体维护的连接池中
type Driver interface {
// Open returns a new connection to the database.
// The name is a string in a driver-specific format.
//
// Open may return a cached connection (one previously
// closed), but doing so is unnecessary; the sql package
// maintains a pool of idle connections for efficient re-use.
//
// The returned connection is only used by one goroutine at a
// time.
Open(name string) (Conn, error)
}
以dsnconnector为例:
type dsnConnector struct {
ds