常è§ÂçÂÂÃ¥Â
³ç³»åÂÂæ°æ®åºÂé½æ¯æÂÂæ ÂÃ¥ÂÂç SQL è¯Âè¨Âï¼ÂæÂÂ以æ 论æ¯ MySQLãÂÂPostgreSQL è¿Âæ¯ SQL Serverï¼ÂæÂÂ们é½å¯以使ç¨ç¸åÂÂç SQL è¯Âå¥æ¥对åÂ
¶è¿Âè¡ÂæÂÂä½ÂãÂÂè¿Âç§ÂæÂÂæ³åÂÂæ ·ä½Âç°å¨ Go è¯Âè¨ÂçÂÂæ°æ®åºÂæÂÂä½Âä¸Âï¼Âå¨ Go è¯Âè¨Âä¸ÂÃ¥ÂÂ
置亠database/sql
Ã¥ÂÂ
ï¼Âå®Âåª对å¤Âæ´é²äºÂä¸Âå¥Âç»Âä¸ÂçÂÂç¼Âç¨ÂæÂ¥å£ï¼Â便å¯以æÂÂä½Âä¸ÂÃ¥ÂÂæ°æ®åºÂãÂÂ
æ¾ÂÂéÂÂç¹讲解 database/sql
设计æÂÂæ³ï¼Âé»Â认读èÂÂ
å·²ç»ÂæÂÂ亠database/sql
使ç¨ç»ÂéªÂï¼Â对亠database/sql
Ã¥ÂÂè½åÂÂä¸Âä¼Â详ç»Âä»Âç»ÂãÂÂå¦ÂæÂÂ你对 database/sql
ä¸ÂçÂÂæÂÂï¼Âå¯以æÂ¥çÂÂæÂÂçÂÂå¦ä¸Âç¯ÂæÂÂç« ãÂÂå¨ Go ä¸Âå¦Âä½Â使ç¨ database/sql æÂ¥æÂÂä½Âæ°æ®åºÂãÂÂãÂÂ
æÂ¥å£设计
é¦ÂÃ¥Â
ÂæÂÂ们æÂ¥çÂÂ丠database/sql
ç®å½Âç»ÂæÂÂé¿ä»Âä¹Âæ ·ï¼Â
go1.20.1/src/database
âÂÂâÂÂâ sql
âÂÂâÂÂâ driver
âÂÂààâÂÂâÂÂâ driver.go
âÂÂààâÂÂâÂÂâ types.go
âÂÂâÂÂâ convert.go
âÂÂâÂÂâ ctxutil.go
âÂÂâÂÂâ sql.go
...
ç¬Âè®°ï¼Âè¿ÂéÂÂ没æÂÂÃ¥ÂÂåºæµÂè¯ÂæÂÂ件ãÂÂ
å¯以åÂÂç°ï¼Âdatabase/sql
å®ÂéÂÂ
ä¸ÂÃ¥ÂÂ
å«亠sql
Ã¥ÂÂ
Ã¥ÂÂÃ¥Â
¶åÂÂÃ¥ÂÂ
driver
ãÂÂ
sql
Ã¥ÂÂ
为æÂÂ们æÂÂä¾ÂäºÂæÂÂä½Âæ°æ®åºÂçÂÂ对象以åÂÂæ¹æ³Âï¼Âdriver
Ã¥ÂÂ
Ã¥ÂÂå®Âä¹ÂäºÂæ°æ®åºÂ驱å¨çÂÂç¼Âç¨ÂæÂ¥å£ï¼Âè¿ÂäºÂæÂ¥å£é½æ¯第ä¸Âæ¹驱å¨åÂÂ
éÂÂè¦Âå®Âç°çÂÂãÂÂ
ç°å¨æÂÂ们ä¸Âèµ·æÂ¥çÂÂ丠driver
Ã¥ÂÂ
æ¯å¦Âä½Â设计çÂÂãÂÂ
å¨ driver
Ã¥ÂÂ
ä¸Âå®Âä¹ÂäºÂä¸Â个 Driver
æÂ¥å£ï¼Â
type Driver interface {
Open(name string) (Conn, error)
}
è¿Â个æÂ¥å£åªæÂÂä¸Â个 Open
æ¹æ³Âï¼Âç¨æ¥建ç«Âä¸Â个æ°æ®åºÂè¿Âæ¥并è¿ÂÃ¥ÂÂãÂÂ
Open
æ¹æ³Âç name
Ã¥ÂÂæ°å³为 DSNï¼Âè¿ÂÃ¥ÂÂå¼ä¸Âç Conn
æÂ¥å£åÂÂ代表äºÂä¸Â个æ°æ®åºÂè¿ÂæÂ¥ï¼Âå®Âä¹Âå¦Âä¸Âï¼Â
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
Conn
æÂ¥å£åÂÂ
å«ä¸Â个æ¹æ³Âï¼Â
Prepare
ç¨æÂ¥é¢Âå¤Âç SQLï¼Âè¿ÂÃ¥ÂÂä¸Â个åÂÂå¤Â好ç SQL è¯Âå¥ãÂÂ
Close
ç¨æÂ¥åÂ
³éÂÂæ°æ®åºÂè¿ÂæÂ¥ãÂÂ
Begin
æ¾ç¶æ¯对äºÂå¡çÂÂæ¯æÂÂãÂÂ
Ã¥Â
¶ä¸ Prepare
è¿Âå Stmt
ç±»åÂÂï¼Âè¿Âä¹Âæ¯ä¸Â个æÂ¥å£ï¼Âå®Âä¹Âå¦Âä¸Âï¼Â
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
Close
ç¨æÂ¥åÂ
³éÂÂ该é¢Âå¤ÂçÂÂè¯Âå¥ãÂÂ
NumInput
è¿Âå SQL ä¸Âå ä½Â符åÂÂæ°çÂÂæ°éÂÂãÂÂ
Exec
å Query
两个æ¹æ³ÂæÂÂ们åÂÂçÂÂæÂÂä¸Âè¿ÂäºÂï¼ÂÃ¥ÂÂå«ç¨æÂ¥æ§衠SQL å½令以åÂÂæ¥询记å½ÂãÂÂè¿Â两个æ¹æ³Âé½æÂ¥æ¶åÂÂæ° []Value
ï¼ÂValue
Ã¥Â
¶å®Âæ¯ any
ç±»åÂÂï¼Âä¹Âå°±æ¯ interface{}
ï¼Âå®Âä¹Âå¦Âä¸Âï¼Â
type Value any
Exec
æ¹æ³Âè¿ÂÃ¥ÂÂç Result
æÂ¥å£å®Âä¹Âå¦Âä¸Âï¼Â
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
LastInsertId
è¿Âå INSERT SQL æÂÂÃ¥Â
¥è®°å½Âç IDãÂÂ
RowsAffected
è¿ÂÃ¥ÂÂÃ¥ÂÂå½±åÂÂè®°å½ÂçÂÂè¡Âæ°ãÂÂ
Query
æ¹æ³Âè¿ÂÃ¥ÂÂç Rows
æÂ¥å£å®Âä¹Âå¦Âä¸Âï¼Â
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
å½ÂæÂÂ们æ§衠SQL æ¥询æ¶ï¼Âå¦ÂæÂÂä¸ÂçÂ¥éÂÂÃ¥ÂÂÃ¥ÂÂï¼Âå¯以使ç¨ rows.Columns()
æÂ¥çÂÂæÂÂæÂÂÃ¥ÂÂÃ¥ÂÂ称åÂÂ表ãÂÂ
Close
ç¨æÂ¥åÂ
³é Rows
çÂÂè¿Â代å¨ï¼ÂÃ¥Â
³éÂÂÃ¥ÂÂæ æ³ÂÃ¥ÂÂ继ç»Âè°Âç¨ Next
æ¥询ä¸Âä¸Âæ¡记å½ÂãÂÂ
è°Âç¨ Next
å¯以å°Âä¸Âä¸Âè¡Âæ°æ®填åÂ
Â
å°æÂÂä¾Âç dest
Ã¥ÂÂçÂÂä¸ÂãÂÂ
Value
å¨ä¸Âé¢已ç»Âä»Âç»Âè¿ÂäºÂï¼Âæ¯ any
ç±»åÂÂãÂÂ
ç°å¨ Conn
æÂ¥å£ä¸Âå®Âä¹Âç Prepare
æ¹æ³Âè¿Âæ¡线æÂÂæ¶ÂÃ¥ÂÂå°çÂÂç±»åÂÂï¼ÂæÂÂ们已ç»Â追æÂ¥å°åºÂäºÂï¼Âæ¯æ¶åÂÂÃ¥ÂÂè¿Â头æÂ¥çÂÂ丠Begin
æ¹æ³Âè¿ÂÃ¥ÂÂç Tx
ç±»åÂÂå®Âä¹ÂäºÂï¼Â
type Tx interface {
Commit() error
Rollback() error
}
Tx
ä¸ÂåºæÂÂæÂÂï¼ÂÃ¥ÂÂæ ·æ¯ä¸Â个æÂ¥å£ï¼ÂÃ¥ÂÂ
å«两个æ¹æ³Âï¼Â
Commit
ç¨æÂ¥æÂÂ交äºÂå¡ãÂÂ
Rollback
ç¨æÂ¥åÂÂæ»ÂäºÂå¡ãÂÂ
è³æ¤ï¼ÂDriver
æÂ¥å£çÂÂ设计就æ¸Â
æ°çÂÂæÂÂå¨ç¼åÂÂäºÂï¼Â
é¤亠Driver
æÂ¥å£ï¼Âå¨ database/sql/driver
Ã¥ÂÂ
ä¸Âï¼Âè¿ÂæÂÂå 个常ç¨æÂ¥å£å®Âä¹Âå¦Âä¸Âï¼Â
type Connector interface {
Connect(context.Context) (Conn, error)
Driver() Driver
}
type Pinger interface {
Ping(ctx context.Context) error
}
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
type ExecerContext interface {
ExecContext(ctx context.Context, query string, args []NamedValue) (Result, error)
}
type Queryer interface {
Query(query string, args []Value) (Rows, error)
}
type QueryerContext interface {
QueryContext(ctx context.Context, query string, args []NamedValue) (Rows, error)
}
Connector
æÂ¥å£ç¨æÂ¥è¿ÂæÂ¥æ°æ®åºÂãÂÂ
Pinger
æÂ¥å£ç¨æÂ¥æ£ÂæÂ¥è¿ÂæÂ¥æ¯å¦è½被æ£确建ç«ÂãÂÂ
è¿Âæ Execer
ãÂÂExecerContext
ãÂÂQueryer
ãÂÂQueryerContext
è¿ 4 个æÂ¥å£ï¼Âæ£好对åºÂäºÂæÂÂ们å¨å©ç¨ database/sql
æ¶æÂÂä½Âæ°æ®åºÂæÂÂ使ç¨çÂÂæ¹æ³ÂãÂÂ
æÂÂæÂÂè¿ÂäºÂæÂ¥å£ï¼Âé½æ¯第ä¸Âæ¹æ°æ®åºÂ驱å¨å è¦Âå®Âç°çÂÂæÂ¥å£ï¼ÂæÂÂäºÂæÂ¥å£æ¯å¯éÂÂçÂÂï¼ÂãÂÂ
çÂÂå°è¿ÂéÂÂï¼Âä½ å¯è½æÂÂ个çÂÂæÂÂï¼Â为ä»Âä¹Âè¿ÂäºÂæÂ¥å£é½åªå®Âä¹Â为åªæÂÂä¸Â个æ¹æ³ÂçÂÂå°ÂæÂ¥å£ï¼Â
è¿Âå ¶å®Âæ¯ Go è¯Âè¨Âä¸ÂçÂÂæ¯ç¨æ³Âï¼Âè¶Âå°ÂçÂÂæÂ¥å£æ½象ç¨Â度è¶Âé«Âï¼ÂæÂÂäºÂ解è¦ï¼Âä¹Âè¶Â容æÂÂ被å®Âç°ï¼Â并ä¸ÂéÂÂ常éÂÂç¨亠Go è¯Âè¨ÂçÂÂç»ÂÃ¥ÂÂæºå¶ãÂÂ
好äºÂï¼ÂÃ¥Â
³äº driver
Ã¥ÂÂ
ä¸ÂæÂ¥å£çÂÂå®Âä¹Âé¨åÂÂ就讲解å°è¿ÂéÂÂï¼ÂÃ¥Â
¶ä»Âç¨çÂÂæ¯Âè¾Âå°ÂæÂ¥å£çÂÂæÂÂå°±ä¸Âå¨è¿ÂéÂÂä»Âç»ÂäºÂï¼ÂæÂÂÃ¥Â
´è¶£çÂÂÃ¥ÂÂå¦å¯以èªè¡Âå°Âè¯ÂéÂÂ
读æºÂç Âå¦习ãÂÂ
以ä¸Âä»Âç»ÂçÂÂè¿ÂäºÂæÂ¥å£åÂ
¨é¨å®Âä¹Âå¨ driver/driver.go
æÂÂ件ä¸Âï¼Âè driver/types.go
æÂÂ件ä¸ÂÃ¥ÂÂç¨æÂ¥å®Âä¹Âç±»åÂÂï¼Â妠Bool
ãÂÂInt32
çÂÂæ¹便ç¨æ¥类åÂÂ转æ¢ï¼Âç±äºÂä¸Âæ¯æ¾ÂÂéÂÂç¹ï¼Âè¿ÂéÂÂä¹Âå°±ä¸Âå¤Âä»Âç»ÂäºÂãÂÂ
代ç Âå®Âç°
çÂÂäºÂ以ä¸Âå ³äºÂæÂ¥å£å®Âä¹ÂçÂÂ讲解ï¼Âä½ å¯è½ä¼Âè§Âå¾ÂæÂÂäºÂäºÂéÂÂé¾éÂÂï¼ÂæÂÂç§Âå¦äºÂä¸Â身åÂÂ夫å´åÂÂæ ä»Âä¸ÂæÂÂçÂÂæÂÂè§ÂãÂÂ
没åÂ
³ç³»ï¼ÂæÂ¥ä¸ÂæÂ¥æÂÂå°Âæ ¹æ®ä¸Â段示ä¾Â代ç Âï¼Â带你深åÂ
¥å° database/sql
çÂÂæºÂç Âä¸Âï¼Âå 深你对 database/sql
Ã¥ÂÂ
çÂÂçÂÂ解ãÂÂ
以ä¸Â示ä¾Âæ¯æÂÂ们使ç¨ database/sql
æÂÂä½ MySQL æÂÂÃ¥Â
¸åÂÂçÂÂåºæ¯ï¼Â
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local")
if err != nil {
panic(err.Error())
}
defer db.Close()
rows, err := db.Query("SELECT id, name FROM user")
if err != nil {
panic(err.Error())
}
defer rows.Close()
for rows.Next() {
var (
id int
name string
)
if err := rows.Scan(id, name); err != nil {
panic(err.Error())
}
fmt.Printf("id: %d, name: %s\n", id, name)
}
}
è¿Â段代ç ÂæÂÂ让åÂÂå¦è æ¸ä¸ÂçÂÂ头èÂÂçÂÂæ¯æÂÂ们以å¿åÂÂçÂÂæ¹å¼Â导堥亠MySQL 驱å¨å ï¼Â
import _ "github.com/go-sql-driver/mysql"
ä½Âå¨å®Âé çÂÂ代ç Âä¸Â并没æÂÂ使ç¨å®ÂãÂÂ
Ã¥Â
¶å®Âï¼Âè¿ÂçÂÂä¼¼æÂÂäºÂå¥ÂæªçÂÂ代ç Â导åÂ
¥çÂÂä½Âç¨ï¼Âå°±éÂÂèÂÂå¨ go-sql-driver/mysql
Ã¥ÂÂ
ç init
å½æ°ä¸Âï¼Â
import "database/sql"
func init() {
sql.Register("mysql", &MySQLDriver{})
}
go-sql-driver/mysql
Ã¥ÂÂ
导åÂ
¥å¹¶ä½¿ç¨ database/sql
Ã¥ÂÂ
ç sql.Register
å½æ°ï¼Âå°Âèª己å®Âç°çÂÂ驱å¨ç¨ÂåºÂ注åÂÂå° database/sql
ä¸ÂãÂÂ
è°Âç¨注åÂÂå½æ°çÂÂ代ç ÂÃ¥ÂÂå¨亠init
å½æ°ä¸Âï¼Âgo-sql-driver/mysql
Ã¥ÂÂ
æ£æ¯å©ç¨äºÂå¿åÂÂ导åÂ
¥æ¶ Go è¯Âè¨Âä¼Âèªå¨è°Âç¨被导åÂ
¥åÂÂ
ç init
æ¹æ³ÂæÂÂ产çÂÂçÂÂå¯ä½Âç¨ï¼ÂæÂ¥å®Âç°驱å¨注åÂÂãÂÂ
注åÂÂ驱å¨å½æ° sql.Register
å®Âç°å¦Âä¸Âï¼Â
var (
driversMu sync.RWMutex
drivers = make(map[string]driver.Driver)
)
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
Ã¥ÂÂ
é¨éÂÂè¿ÂÃ¥Â
¨å±ÂäºÂæÂ¥éÂÂÃ¥ÂÂé driversMu
ä¿Âè¯ÂäºÂ并åÂÂæÂÂä½ÂçÂÂå®ÂÃ¥Â
¨æ§ãÂÂå¨å éÂÂçÂÂæ¡件ä¸Âï¼Âå° mysql
驱å¨ä¿ÂÃ¥ÂÂå¨ drivers
è¿Â个åÂ
¨å±Âç map ç±»åÂÂÃ¥ÂÂéÂÂä¸Âï¼Â以 mysql
为 key
ï¼Â驱å¨对象为 value
ãÂÂ
è¿Âå°±æ¯为ä»Âä¹Âï¼ÂæÂÂ们è½å¤Â使ç¨ sql.Open
å½æ°ä¸Âæ°æ®åºÂ建ç«Âè¿ÂæÂ¥çÂÂÃ¥ÂÂå ãÂÂ
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)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
Open
å½æ°æÂ¥æ¶两个åÂÂæ°ï¼Â驱å¨åÂÂ称å DSNãÂÂ
å¨ Open
å½æ°åÂÂ
é¨ï¼Âé¦ÂÃ¥Â
Âä»ÂÃ¥Â
¨å±ÂÃ¥ÂÂé drivers
ä¸Âè·åÂÂ驱å¨对象ãÂÂèÂÂæÂÂ们è°Âç¨ sql.Open
å½æ°æ¶ï¼Âä¼ éÂÂçÂÂ第ä¸Â个åÂÂæ°æ¯ mysql
ï¼Âè¿ÂÃ¥ÂÂ好丠go-sql-driver/mysql
Ã¥ÂÂ
ä¸Â注åÂÂçÂÂ驱å¨åÂÂ称对åºÂï¼ÂæÂÂ以è½å¤Âè·åÂÂå° MySQL 驱å¨ç¨ÂåºÂãÂÂ
æÂ¥çÂÂï¼Â代ç Âä¸ÂéÂÂè¿Âç±»åÂÂæÂÂè¨Âï¼Âå¤æÂÂ驱å¨对象 driveri
æ¯å¦为 driver.DriverContext
ç±»åÂÂãÂÂ
æ¯çÂÂè¯Âå°±åÂ
Âè°Âç¨驱å¨对象ç OpenConnector
æ¹æ³Âå¾Âå° Connector
ç±»åÂÂçÂÂ对象ï¼Âç¶åÂÂÃ¥ÂÂ使ç¨ OpenDB
æÂÂå¼Âæ°æ®åºÂè¿ÂæÂ¥ãÂÂ
driver.DriverContext
æÂ¥å£å®Âä¹Âå¦Âä¸Âï¼Â
type DriverContext interface {
OpenConnector(name string) (Connector, error)
}
åªåÂÂ
å«亠OpenConnector
æ¹æ³Âï¼Âè¿Â个æ¹æ³Âè¿Âå Connector
æÂ¥å£类åÂÂãÂÂ
Connector
æÂ¥å£åÂÂæÂÂå·²ç»Â讲è¿Âï¼ÂæÂÂ们å¯以åÂÂÃ¥ÂÂ顾ä¸Âå®ÂçÂÂå®Âä¹Âï¼Â
type Connector interface {
Connect(context.Context) (Conn, error)
Driver() Driver
}
è¿Â个æÂ¥å£å®Âä¹ÂäºÂ两个æ¹æ³ÂÃ¥ÂÂå«ç¨æÂ¥è¿ÂæÂ¥æ°æ®åºÂÃ¥ÂÂè·åÂÂ驱å¨对象ãÂÂ
èÂÂå¦Âæ driveri
ä¸Âæ¯ driver.DriverContext
ç±»åÂÂï¼ÂÃ¥ÂÂéÂÂè¦ÂÃ¥Â
ÂæÂÂé ä¸Â个 dsnConnector
对象ï¼Âç¶åÂÂÃ¥ÂÂ使ç¨ OpenDB
å½æ°æÂÂå¼Âæ°æ®åºÂè¿ÂæÂ¥ãÂÂ
dsnConnector
æ¯ä¸Â个ç»ÂæÂÂä½Âï¼Âå®Âä¹ÂéÂÂ常ç®ÂÃ¥ÂÂï¼Â
type dsnConnector struct {
dsn string
driver driver.Driver
}
åªå å«亠DSN Ã¥ÂÂ驱å¨对象ãÂÂ
并ä¸Âå®ÂÃ¥ÂÂæ¶ä¹Âå®Âç°亠Connector
æÂ¥å£ï¼Â
func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) {
return t.driver.Open(t.dsn)
}
func (t dsnConnector) Driver() driver.Driver {
return t.driver
}
æÂ¥ä¸ÂæÂ¥ï¼ÂæÂÂ们çÂÂç OpenDB
å½æ°æ¯å¦Âä½Âå®Âä¹ÂçÂÂï¼Â
func OpenDB(c driver.Connector) *DB {
ctx, cancel := context.WithCancel(context.Background())
db := &DB{
connector: c,
openerCh: make(chan struct{}, connectionRequestQueueSize),
lastPut: make(map[*driverConn]string),
connRequests: make(map[uint64]chan connRequest),
stop: cancel,
}
go db.connectionOpener(ctx)
return db
}
å¨ OpenDB
å½æ°åÂÂ
é¨å®Âä¾ÂÃ¥ÂÂäºÂä¸Â个 *sql.DB
ç»ÂæÂÂä½ÂæÂÂéÂÂ并è¿ÂÃ¥ÂÂãÂÂè¿Â个ç»ÂæÂÂä½Âç± database/sql
Ã¥ÂÂ
æÂÂä¾Âï¼Âæ¯ç»Âä¸Âç¨æ·ç¼Âç¨ÂæÂ¥å£çÂÂÃ¥Â
³é®ç»ÂæÂÂä½Âï¼ÂæÂÂ们åÂÂç»ÂçÂÂæ¥询æÂÂä½Âï¼Âå°±æ¯è°Âç¨äºÂè¿Â个对象ä¸ÂçÂÂæ¹æ³ÂãÂÂ
è¿ÂéÂÂå®Âä¾Âå *sql.DB
对象æ¶ï¼Â并ä¸Âä¸Âä¼Âç«Âå³建ç«Âæ°æ®åºÂè¿ÂæÂ¥ï¼Âè¿ÂæÂ¥ä¼Âå¨éÂÂè¦Âæ¶被延è¿Â建ç«ÂãÂÂ
å¨ sql.DB
ç»ÂæÂÂä½Âä¸Âï¼ÂæÂÂ们éÂÂè¦ÂÃ¥Â
³æ³¨çÂÂæ¯ openerCh
å±Âæ§ï¼Âè¿Âæ¯ä¸Â个 Channel 对象ï¼Âæ¯ä¸Â个ç¨æÂ¥ä¿ÂÃ¥ÂÂè¿Âæ¥请æ±ÂçÂÂéÂÂÃ¥ÂÂï¼Âç¨ÂÃ¥ÂÂæÂÂ们å°Âè§Âå°å®ÂçÂÂÃ¥Â
³é®ä½Âç¨ãÂÂ
db
对象åÂÂ建åÂÂï¼ÂéÂÂè¿ go db.connectionOpener(ctx)
Ã¥ÂÂ猯ç¨äºÂä¸Â个åÂÂç¨Âï¼Âç¨æÂ¥å¤ÂçÂÂ建ç«Âè¿ÂæÂ¥çÂÂ请æ±ÂãÂÂ
å½æ°æÂÂç»Âè¿ÂÃ¥ÂÂäºÂè¿Â个 *sql.DB
ç±»åÂÂç db
对象ï¼Âæ¤对象æ¯并åÂÂå®ÂÃ¥Â
¨çÂÂï¼Âæ¯æÂÂå¤Â个 Goroutine Ã¥ÂÂæ¶æÂÂä½Âï¼Â并ä¸Âç»´æ¤äºÂèª己çÂÂ空é²è¿Âæ¥池ãÂÂ
OpenDB
å½æ°åªåºÂ被è°Âç¨ä¸Â次ï¼Âä¸Âå¾Âå°ÂéÂÂè¦Âç¨æ·主å¨åÂ
³éÂÂè¿ÂæÂ¥ãÂÂ
db.connectionOpener
æ¹æ³Âå®Âç°å¦Âä¸Âï¼Â
func (db *DB) connectionOpener(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-db.openerCh:
db.openNewConnection(ctx)
}
}
}
è¿ÂéÂÂä»Â
Ã¥ÂÂ
å«ä¸Â个永ä¸ÂéÂÂåºçÂÂæ éÂÂ循ç¯ï¼Âå½ db.openerCh
è¿Â个 Channel æÂÂå¼æ¶ï¼Â代ç Âä¼Âè¿ÂÃ¥Â
Â¥ db.openNewConnection
å½æ°çÂÂè°Âç¨ãÂÂ
func (db *DB) openNewConnection(ctx context.Context) {
ci, err := db.connector.Connect(ctx)
...
dc := &driverConn{
db: db,
createdAt: nowFunc(),
returnedAt: nowFunc(),
ci: ci,
}
if db.putConnDBLocked(dc, err) {
db.addDepLocked(dc, dc)
}
...
}
å¨ db.openNewConnection
å½æ°çÂÂ第ä¸Âè¡Â代ç Âä¸Âï¼Âdb.connector
å±Âæ§æ¯å¨ä¹ÂÃ¥ÂÂè°Âç¨ OpenDB
æ¶è¿Âè¡ÂèµÂå¼çÂÂä¸Â个 driver.Connector
æÂ¥å£类åÂÂ对象ï¼Âè¿Âè®°å¾ÂÃ¥ÂÂæÂÂ讲ç dsnConnector
Ã¥ÂÂï¼Âï¼Âè°Âç¨å®Âç Connect
æ¹æ³Âå°±å¯以ä¸Âæ°æ®åºÂ建ç«Âè¿ÂæÂ¥äºÂãÂÂ
ä¹ÂÃ¥ÂÂè°Âç¨ç db.putConnDBLocked(dc, err)
æ¹æ³Âä½Âç¨æ¯å°Âè¿Â个è¿ÂæÂ¥æ¾åÂ
¥æ°æ®åºÂ空é²è¿Âæ¥池ä¸Âï¼Âdb.freeConn
å±Âæ§ï¼ÂãÂÂ
è³æ¤ï¼ÂæÂÂ们å¾Âå°äºÂ两æ¡å½æ°è°Âç¨线ï¼Â
å¨驱å¨åÂÂ
go-sql-driver/mysql
ä¸Âï¼ÂéÂÂè¿ sql.Register
è¿Âè¡Â驱å¨ç¨ÂåºÂ注åÂÂãÂÂ
å¨ database/sql
ä¸Âï¼ÂæÂÂ们è°Âç¨ sql.OpenDB
æÂ¥å¼Âå¯æ°æ®åºÂè¿ÂæÂ¥ï¼Âè¿Âä¸Âä¼Âç«Âå»建ç«Âè¿ÂæÂ¥ï¼ÂèÂÂæ¯éÂÂè¿Âå¼Âå¯æ°ç Goroutine é»å¡Âå¨ db.openerCh
Channel ä¸Âï¼ÂçÂÂå¾Â
建ç«Âè¿Âæ¥请æ±ÂãÂÂ
é£ä¹ÂæÂ¥ä¸ÂæÂ¥ï¼Âä½Âæ¶触å *sql.DB.connectionOpener
å½æ°丠<-db.openerCh
è¿Â个 case
ï¼Âå°±æ¯æÂÂ们è¦Âç Â究çÂÂéÂÂç¹äºÂãÂÂ
æÂÂ们å¯以顺çÂÂ示ä¾Â代ç Â继ç»Âå¾Âä¸ÂçÂÂãÂÂ
å¨示ä¾Âä¸Âï¼ÂæÂ¥ä¸Âæ¥使ç¨ db.Query("SELECT id, name FROM user")
æ¹æ³ÂæÂ¥æ¥询 user
è®°å½ÂãÂÂ
*sql.DB.Query
æ¹æ³Âå®Âä¹Âå¦Âä¸Âï¼Â
func (db *DB) Query(query string, args ...any) (*Rows, error) {
return db.QueryContext(context.Background(), query, args...)
}
å®Âç´æÂ¥è°Âç¨亠*sql.DB.QueryContext
ï¼Â
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
var rows *Rows
var err error
err = db.retry(func(strategy connReuseStrategy) error {
rows, err = db.query(ctx, query, args, strategy)
return err
})
return rows, err
}
*sql.DB.QueryContext
æ¹æ³ÂÃ¥ÂÂ
é¨åÂÂè°Âç¨亠db.query
æ¹æ³Âï¼Â
func (db *DB) query(ctx context.Context, query string, args []any, strategy connReuseStrategy) (*Rows, error) {
dc, err := db.conn(ctx, strategy)
if err != nil {
return nil, err
}
return db.queryDC(ctx, nil, dc, dc.releaseConn, query, args)
}
å¨ db.query
æ¹æ³ÂÃ¥ÂÂ
é¨ï¼Âé¦ÂÃ¥Â
Âè°Âç¨亠db.conn
æ¹æ³ÂãÂÂdb.conn
顾åÂÂæÂÂä¹Âï¼Âå°±æ¯ç¨æ¥建ç«Âæ°æ®åºÂè¿ÂæÂ¥çÂÂï¼Âå®Âä¹Âå¦Âä¸Âï¼Â
func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
last := len(db.freeConn) - 1
if strategy == cachedOrNewConn && last >= 0 {
conn := db.freeConn[last]
...
return conn, nil
}
...
ci, err := db.connector.Connect(ctx)
if err != nil {
db.mu.Lock()
db.numOpen-- // correct for earlier optimism
db.maybeOpenNewConnections()
db.mu.Unlock()
return nil, err
}
db.mu.Lock()
dc := &driverConn{
db: db,
createdAt: nowFunc(),
returnedAt: nowFunc(),
ci: ci,
inUse: true,
}
db.addDepLocked(dc, dc)
db.mu.Unlock()
return dc, nil
}
è¿ÂéÂÂæÂÂçÂÂçÂ¥äºÂä¸ÂäºÂ代ç Âï¼ÂåªåÂÂåºäºÂæ¯Âè¾ÂéÂÂè¦ÂçÂÂé»è¾ÂãÂÂ
å¨å½æ°åÂÂ
é¨ï¼Âé¦ÂÃ¥Â
Âä¼Âå°Âè¯Âä» db.freeConn
空é²è¿Âæ¥池ä¸Âè·åÂÂè¿ÂæÂ¥ãÂÂ
å¦ÂæÂÂ没æÂÂ空é²è¿ÂæÂ¥ï¼ÂÃ¥ÂÂè°Âç¨ db.connector.Connect
æÂ¥è·åÂÂæ°çÂÂæ°æ®åºÂè¿ÂæÂ¥ãÂÂ
å½Âè·åÂÂè¿Âæ¥失败ï¼Âä¼Âè°Âç¨ db.maybeOpenNewConnections()
æ¹æ³Â并è¿ÂÃ¥ÂÂéÂÂ误ãÂÂ
è¿Â个 db.maybeOpenNewConnections()
æ¹æ³Âæ¯æÂÂ们è¦ÂÃ¥Â
³æ³¨çÂÂéÂÂç¹ï¼Âå®Âä¹Âå¦Âä¸Âï¼Â
func (db *DB) maybeOpenNewConnections() {
numRequests := len(db.connRequests)
if db.maxOpen > 0 {
numCanOpen := db.maxOpen - db.numOpen
if numRequests > numCanOpen {
numRequests = numCanOpen
}
}
for numRequests > 0 {
db.numOpen++ // optimistically
numRequests--
if db.closed {
return
}
db.openerCh <- struct{}{}
}
}
å¯以åÂÂç°ï¼Âæ£æ¯å¨è¿Â个æ¹æ³ÂÃ¥ÂÂ
é¨ï¼Âè°Âç¨亠db.openerCh <- struct{}{}
为 Channel Ã¥ÂÂéÂÂæ°æ®ãÂÂ
å½ db.openerCh
æÂÂå¼æ¶ï¼Âä¼Â被åÂÂæÂÂ讲解çÂÂéÂÂè¿ÂÃ¥ÂÂÃ¥ÂÂç¨Âè°Âç¨ç *sql.DB.connectionOpener
å½æ°æ¶Âè´¹ï¼Â以æ¤æ¥触åÂÂå¼ÂæÂ¥è·åÂÂæ°æ®åºÂè¿ÂæÂ¥æÂÂä½ÂãÂÂ
Ã¥ÂÂæÂÂæÂÂæÂÂå°ï¼Âå¼ÂæÂ¥åÂÂ建çÂÂæ°æ®åºÂè¿ÂæÂ¥ä¼Â被æ¾åÂ
¥ç©ºé²è¿Âæ¥池db.freeConn
ä¸ÂãÂÂ
æ¤æ¶ï¼ÂæÂÂ们åÂÂ次åÂÂå° db.query
æ¹æ³Â被è°Âç¨çÂÂå°æ¹ï¼ÂæÂ¥éÂÂæ°审è§Â丠*sql.DB.QueryContext
æ¹æ³ÂçÂÂå®Âä¹Âï¼Â
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
var rows *Rows
var err error
err = db.retry(func(strategy connReuseStrategy) error {
rows, err = db.query(ctx, query, args, strategy)
return err
})
return rows, err
}
è¿ÂéÂÂ并ä¸Âæ¯ç®ÂÃ¥ÂÂçÂÂç´æÂ¥è°Âç¨ db.query
ï¼ÂèÂÂæ¯å°ÂÃ¥Â
¶æ¾åÂ
¥äº db.retry
æ¹æ³Âä¸Âè°Âç¨ãÂÂ
顾åÂÂæÂÂä¹Âï¼Âdb.retry
æ¹æ³Âæ¯ç¨æÂ¥è¿Âè¡ÂéÂÂè¯ÂæÂÂä½ÂçÂÂï¼Âå¦Âæ db.query
è°Âç¨失败ï¼ÂÃ¥ÂÂä¼ÂéÂÂè¯Âä¸Â次ãÂÂ
è¿Âå°±ä½Âç°äºÂå½Âè°Âç¨ db.connector.Connect(ctx)
失败æ¶ï¼Âè°Âç¨ db.maybeOpenNewConnections()
æ¹æ³Âå¼Âæ¥建ç«Âè¿ÂæÂ¥çÂÂæÂÂä¹ÂãÂÂ
å 为å¦ÂæÂÂ第ä¸Â次åÂÂ建è¿Âæ¥失败ï¼Âå db.retry
ä¼Âè¿Âè¡ÂéÂÂè¯Âï¼Âä¸Â次éÂÂè¯ÂçÂÂæ¶åÂÂï¼ÂÃ¥ÂÂ次è¿ÂÃ¥Â
Â¥ db.conn
æ¹æ³Âï¼Âå¦ÂæÂÂå¼Âæ¥建ç«Âè¿Âæ¥已ç»Âå®ÂæÂÂï¼ÂÃ¥ÂÂå¯以ç´æÂ¥ä»Â空é²è¿Âæ¥池db.freeConn
ä¸Âè·åÂÂæ°æ®åºÂè¿ÂæÂ¥ãÂÂå³使å¼Âæ¥建ç«Âè¿ÂæÂ¥æÂ¥ä¸ÂÃ¥ÂÂå®ÂæÂÂï¼Âé£ä¹Â空é²è¿Âæ¥池ä¹Âä¼ÂæÂÂä¸Â个æ°çÂÂè¿Âæ¥被åÂÂ建ï¼Âä¸Â次æÂÂå¦å¤Âä¸Â个请æ±Âè¿ÂæÂ¥ï¼Âä¹Âè½å¤Âä»Â空é²è¿Âæ¥池ä¸Âè·åÂÂè¿ÂæÂ¥ãÂÂè¿Â个æÂÂä½Âè½å¤ÂæÂÂÃ¥ÂÂç¨ÂåºÂçÂÂæ§è½ãÂÂ
è³æ¤ï¼Âdatabase/sql
Ã¥ÂÂ
丠sql.Open
å *sql.DB.Query
两æ¡å½æ°è°Âç¨线ï¼ÂæÂÂ们就æÂÂæ¸Â
æ¥ÂäºÂï¼Â
è¿Â两æ¡å½æ°è°Âç¨线éÂÂä¿¡çÂÂÃ¥Â
³é®ï¼Âå°±æ¯ db.openerCh
æÂÂå¨ãÂÂ
ç°å¨ï¼Âä¸Âå¾丠*sql.DB.Query
è¿Âæ¡å½æ°è°Âç¨线æÂÂ们å¯ç¬没æÂÂæÂÂæ¸Â
æ¥ÂçÂÂå°±åªå©丠*sql.DB.queryDC
çÂÂè°Âç¨äºÂãÂÂ
*sql.DB.queryDC
å®Âä¹Âå¦Âä¸Âï¼Â
func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any) (*Rows, error) {
queryerCtx, ok := dc.ci.(driver.QueryerContext)
var queryer driver.Queryer
if !ok {
queryer, ok = dc.ci.(driver.Queryer)
}
if ok {
var nvdargs []driver.NamedValue
var rowsi driver.Rows
var err error
withLock(dc, func() {
nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
if err != nil {
return
}
rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
})
...
}
...
}
è¿ÂéÂÂæÂÂè¨Â亠*driverConn
ä¸Âæº带çÂÂæ¥询对象æ¯ driver.QueryerContext
è¿Âæ¯ driver.Queryer
ï¼Â并å°ÂæÂÂè¨Âç»ÂæÂÂä¼ éÂÂç» ctxDriverQuery
å½æ°ãÂÂ
ctxDriverQuery
å®Âä¹Âå¦Âä¸Âï¼Â
func ctxDriverQuery(ctx context.Context, queryerCtx driver.QueryerContext, queryer driver.Queryer, query string, nvdargs []driver.NamedValue) (driver.Rows, error) {
if queryerCtx != nil {
return queryerCtx.QueryContext(ctx, query, nvdargs)
}
dargs, err := namedValueToValue(nvdargs)
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
return nil, ctx.Err()
}
return queryer.Query(query, dargs)
}
å¨ ctxDriverQuery
å½æ°åÂÂ
é¨ï¼Âæ ¹æ®æ¥询对象类åÂÂçÂÂä¸ÂÃ¥ÂÂï¼Âè°Âç¨亠queryerCtx.QueryContext
æ queryer.Query
ãÂÂ
è¿Â个æÂÂä½Âæ£æ¯å¨è°Âç¨驱å¨ç¨ÂåºÂ对åºÂç QueryContext
æ Query
æ¹æ³ÂãÂÂ
ä¸Â管æ¯ driver.QueryerContext
è¿Âæ¯ driver.Queryer
ï¼Âé½æ¯ database/sql/driver
ä¸Âå®Âä¹ÂçÂÂæÂ¥å£类åÂÂï¼Âdatabase/sql
Ã¥ÂÂ
é¨æ£æ¯éÂÂè¿Â使ç¨æÂ¥å£类åÂÂï¼ÂæÂ¥å®Âç°è·Â驱å¨ç¨Â庠go-sql-driver/mysql
çÂÂ解è¦ãÂÂ
è¿Âæ ·ï¼Âdatabase/sql
ä¸Âç´æ¥跠go-sql-driver/mysql
ä¸Âå®Âä¹ÂçÂÂÃ¥Â
·ä½Âç±»åÂÂæÂÂ交éÂÂï¼ÂäºÂèÂÂ
éÂÂè¿ database/sql/driver
è¿Â个ä¸Âé´å±Âæ¥交äºÂï¼Âè¿Â便æ¯ Go è¯Âè¨ÂæÂ¥å£ç¨æ³ÂçÂÂç²¾é«ÂæÂÂå¨ãÂÂ
ç°å¨ï¼ÂæÂÂ们çÂÂå½æ°è°Âç¨线路å¾就已ç»Âå®Âæ´äºÂãÂÂ
æ»ç»Â
æ¾ÂÂ带大家ä¸Âèµ·å¦习亠database/sql
Ã¥ÂÂ
çÂÂ设计æÂÂæ³ï¼Âdatabase/sql/driver
ç¨æÂ¥å®Âä¹Âå®Âä¹Â驱å¨éÂÂè¦Âå®Âç°çÂÂæÂ¥å£ï¼Âdatabase/sql
Ã¥ÂÂ为ç¨æ·æÂÂä¾ÂäºÂæÂÂä½Âæ°æ®åºÂçÂÂæ¹æ³ÂãÂÂ
è¿ÂéÂÂæ¶ÂÃ¥ÂÂäºÂä¸Â个使ç¨ init
å½æ°çÂÂæÂÂå·§ï¼Âå©ç¨ init
å½æ°çÂÂå¯ä½Âç¨ï¼Âå¯以å®Âç°ä¸Âæ¹ database/sql
ä»»ä½Â代ç ÂçÂÂæÂÂ
åµä¸Âï¼ÂåªéÂÂ覠import
驱å¨ç¨ÂåºÂï¼Âå°±è½注åÂÂ驱å¨ç¨ÂåºÂçÂÂæÂÂæÂÂÃ¥ÂÂè½ãÂÂ
éÂÂè¿Âä¸Â个ç®ÂÃ¥ÂÂçÂÂ示ä¾Âç¨ÂåºÂï¼ÂæÂÂ们ä¸Âèµ·éÂÂ
读亠database/sql
Ã¥ÂÂ
çÂÂé¨åÂÂæºÂç Âï¼Â以 *sql.DB.Query
æ¹æ³Âä½Â为示ä¾Âï¼ÂæÂ¥çÂÂ亠database/sql
æÂÂç»Âæ¯å¨ä½Âå¤Âè°Âç¨驱å¨ç¨ÂåºÂ对åºÂæ¹æ³ÂçÂÂãÂÂæÂÂç Âå¼ÂçÂÂï¼Âå¦ÂæÂÂ你对åÂ
¶ä»Âæ¹æ³ÂæºÂç Âä¹ÂæÂÂÃ¥Â
´è¶£ï¼Âå¯以顺çÂÂæÂÂ讲解çÂÂæÂÂ路继ç»Âæ·±åÂ
¥å¦习ãÂÂ
database/sql
Ã¥ÂÂ
ç»Âä¸Â亠Go è¯Âè¨ÂæÂÂä½Âæ°æ®åºÂçÂÂç¼Âç¨ÂæÂ¥å£ï¼Âé¿åÂ
ÂäºÂæÂÂä½Âä¸ÂÃ¥ÂÂæ°æ®åºÂéÂÂè¦Âå¦习å¤Â奠API çÂÂçªÂå¢ÂãÂÂ
è®°ä½Âï¼Âå¨ Go è¯Âè¨Âä¸Â使ç¨æÂ¥å£æ¥解è¦æ¯æ¯ç¨æ¹æ³Âï¼Âä½ ä¸Âå®Âè¦ÂæÂÂæ¡ãÂÂæªæÂ¥æÂÂ们讲解å¦Âä½Âç¼ÂÃ¥ÂÂÃ¥ÂÂå ÂæµÂè¯Â代ç ÂçÂÂæ¶åÂÂï¼Âè¿Âä¼Âç¨å°ãÂÂ
注æÂÂï¼Âæ¾ÂÂ讲解ç database/sql
Ã¥ÂÂ
æºÂç ÂçÂÂæ¬为 Go 1.20.1ï¼ÂÃ¥Â
¶ä»ÂçÂÂ振è½æÂÂæÂÂä¸ÂÃ¥ÂÂãÂÂ
å¸ÂæÂÂæ¤æÂÂè½对你æÂÂæÂÂ帮å©ãÂÂ
èÂÂç³»æÂÂ
- 微信ï¼Âjianghushinian
- é®箱ï¼Âjianghushinian007@outlook.com
- Ã¥ÂÂ客å°åÂÂï¼Âjianghushinian.cn/
Ã¥ÂÂèÂÂ
- database/sql æºÂç Âï¼Âgithub.com/golang/go/tâ¦
- MySQL Driverï¼Âgithub.com/go-sql-drivâ¦
- å¨ Go ä¸Âå¦Âä½Â使ç¨ database/sql æÂ¥æÂÂä½Âæ°æ®åºÂï¼Âjianghushinian.cn/2023/06/05/â¦