轉,原文:https://www.jianshu.com/p/3b0b3a4c83da
---------------
數據庫操作是一個應用必不可少的部分,但是我們很多時候對golang的sql包僅僅是會用,這是不夠的。每一條語句的執行,它的背後到底發生了什麼。各式各樣對sql包的封裝,是不是有必要的,有沒有做無用功?
這是go to database package
系列文章的第一篇。本系列將按照程序中使用sql包的順序來展開
先來看一段簡短的代碼:
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
)
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if nil != err {
panic(err)
}
age := 18
rows,err := db.Query(`SELECT name,age FROM person where age > ?`, age)
if nil != err {
panic(err)
}
defer rows.Close()
for rows.Next() {
var name string
var age int
err := rows.Scan(&name, &age)
if nil != err {
panic(err)
}
fmt.Println(name, age)
}
}
這應該是最簡單的使用場景了。本文也會按照以上代碼,逐句展開。
import _ "somedriver"
是在幹什麼
先來看一下golang官方文檔的說法:
To import a package solely for its side-effects (initialization), use the blank identifier as explicit package name:
import _ "lib/math"
也就是說,import _ "somedriver"
僅僅是想調用somedriver
包的init
方法。那麼我們可以一起來看看go-sql-driver/mysql
的init
方法。它非常簡單:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
只有1行,確實非常的簡單。調用sql的Register
方法註冊了一個名爲mysql
的數據庫驅動,而驅動本身就是&MySQLDriver{}
。
那我們再看看sql
包中的Register
方法:
// Register makes a database driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
drivers[name] = driver
}
Register
的第二個參數接收一個driver.Driver
的interface,因此go-sql-driver/mysql
包中的&MySQLDriver
必須實現driver.Driver
規定的一系列方法(當然它肯定實現了)。
Register
函數如果發現名爲name的driver已經註冊了,就會觸發panic,否則就進行註冊。註冊其實很簡單,drivers[name] = driver
。
drivers
是一個map
drivers = make(map[string]driver.Driver)
所以簡單來說,import _ "somedriver"
其實就是調用sql.Register
註冊一個實現了driver.Driver
接口的實例。
驅動給sql包提供了最基本的支持,sql包最終與數據庫打交道的操作都是通過driver完成的。其實不應該說sql包,而應該說是DB
實例。
在上面程序main函數的一開始,執行sql.Open
拿到了一個DB
實例,那麼什麼是DB
實例,sql.Open
又幹了什麼?
sql.Open
是在幹什麼
看一下官方文檔的介紹:
func Open(driverName, dataSourceName string) (*DB, error)
//Open opens a database specified by its database driver name and a driver-specific data source name, usually consisting of at least a database name and connection information.
//Most users will open a database via a driver-specific connection helper function that returns a *DB. No database drivers are included in the Go standard library. See https://golang.org/s/sqldrivers for a list of third-party drivers.
//Open may just validate its arguments without creating a connection to the database. To verify that the data source name is valid, call Ping.
//The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.
簡單來說,Open返回一個DB
實例,DB實例
引用了由driverName
指定的數據庫驅動程序。DB
本身維護了數據庫連接池,是線程安全的。
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
}
db := &DB{
driver: driveri,
dsn: dataSourceName,
openerCh: make(chan struct{}, connectionRequestQueueSize),
lastPut: make(map[*driverConn]string),
}
go db.connectionOpener()
return db, nil
}
Open方法:
- 根據
driverName
拿到對應的driver - 根據driver和dataSourceName生成一個DB實例
- 另起一個goroutine來執行某種任務A
如果對goroutine比較敏感的同學可能會猜到go db.connectionOpener()
是在幹嘛。在go中大多數情況下新開一個goroutine都是在:
- 監聽某個channel
- 往某個channel發消息
根據上面的代碼不難猜測,connectionOpener
和opennerCh
有關。看名字也很容易看出,connectionOpener
翻譯過來就是連接創建者
,負責創建連接。看看代碼吧:
// Runs in a separate goroutine, opens new connections when requested.
func (db *DB) connectionOpener() {
for range db.openerCh {
db.openNewConnection()
}
}
每當從openerCh
取到一條消息,connectionOpener
就創建一個連接。
如何創建連接其實很簡單,就是調用Driver提供的Open
方法,具體先暫時不展開了。(不展開的這個決定,和golang的sql包是很吻合的,因爲sql包對Open一個連接的處理,僅僅是定義了一個接口,讓驅動去實現。也就是說,在邏輯上這裏需要Open一個新連接,具體怎麼做我不管,Driver你提供Open接口,返回給我我要的就行。)
整個DB可以畫一張圖來理解。
當然DB實例還有很多其它細節,但是對於sql.Open
方法來說,以上就夠了。總結一下,sql.Open
會根據driverName和dataSourceName生成一個DB實例,並且另起一個goroutine來負責新建連接(監聽openerCh的“新建連接請求”)。
在這裏可以看出,執行sql.Open
,僅僅返回了DB
實例,但無法得知是否真的和數據庫成功連接。按照文檔的說法,如果要確認是否和數據庫真的連接上了,需要執行Ping
方法:
Open may just validate its arguments without creating a connection to the database. To verify that the data source name is valid, call Ping.
成功拿到DB對象之後,我們就可以操作數據庫了。
下一篇,我們的主題將是連接池的維護
和what's behind the db.Query command
作者:suoga
鏈接:https://www.jianshu.com/p/3b0b3a4c83da
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。