一、介绍
database/sql 采用策略模式,每个driver实现驱动,提供一个注册连接词。
var (
driversMu sync.RWMutex
drivers = make(map[string]driver.Driver)
)
// 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
}
当我们使用不同的数据库的时候,只要把使用的驱动注册进去就可以了。
比如在 github.com/go-sql-driver/mysql mysql驱动库里。
有一段代码
github.com/go-sql-driver/mysql/driver.go
func init() {
sql.Register("mysql", &MySQLDriver{})
}
这里注册是在drivers的map的一个key,关联一个driver。
二、使用流程
2.1 sql.Open()创建连接池句柄sql.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)
}
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
}
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
}
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{}
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdle int // 最大空闲连接数
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.
}
注意sql.Open()只是初始化一个 sql.DB,此时并不会真正的创建连接。
初始化内容,设置driver的参数,解析dns等。
2.2设置数据库连接参数
func TestOpen(t *testing.T) {
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database?charset=utf8")
t.Log(db, err)
db.SetMaxIdleConns(10) //最大空闲连接数
db.SetMaxOpenConns(11) //最大连接数
db.SetConnMaxLifetime(5 * time.Second) //最大连接时间
}
打印输出:
&{0 0xc0000a2028 0 {0 0} [] map[] 0 0 0xc00008e2a0 false map[] map[] 10 11 5000000000 <nil> 0 0 0 0x10d28a0} <nil>
2.3访问数据库
当我们初始化后,我们ping一下数据库,然后可以看到是否连接通。
源码如下:src/database/sql/sql.go
func (db *DB) Ping() error {
return db.PingContext(context.Background())
}
func (db *DB) PingContext(ctx context.Context) error {
var dc *driverConn
var err error
for i := 0; i < maxBadConnRetries; i++ {
dc, err = db.conn(ctx, cachedOrNewConn)
if err != driver.ErrBadConn {
break
}
}
if err == driver.ErrBadConn {
dc, err = db.conn(ctx, alwaysNewConn)
}
if err != nil {
return err
}
return db.pingDC(ctx, dc, dc.releaseConn)
}
func (db *DB) pingDC(ctx context.Context, dc *driverConn, release func(error)) error {
var err error
if pinger, ok := dc.ci.(driver.Pinger); ok {
withLock(dc, func() {
err = pinger.Ping(ctx)
})
}
release(err)
return err
}
我们使用一下:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"testing"
"time"
)
func TestOpen(t *testing.T) {
//1 获取sql.DB
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database?charset=utf8")
t.Log(db, err)
//2 设置连接参数
db.SetMaxIdleConns(10) //最大空闲连接数
db.SetMaxOpenConns(11) //最大连接数
db.SetConnMaxLifetime(5 * time.Second) //最大连接时间
t.Log(db, err)
//3 访问数据库
err = db.Ping()
t.Log(db, err)
}
输出如下:
&{0 0xc00010a028 0 {0 0} [] map[] 0 0 0xc000122240 false map[] map[] 0 0 0 <nil> 0 0 0 0x10d29c0} <nil>
&{0 0xc00010a028 0 {0 0} [] map[] 0 0 0xc000122240 false map[] map[] 10 11 5000000000 <nil> 0 0 0 0x10d29c0} <nil>
&{0 0xc00010a028 0 {0 0} [0xc0001501c0] map[] 0 1 0xc000122240 false map[0xc0001501c0:map[0xc0001501c0:true]] map[] 10 11 5000000000 0xc000122420 0 0 0 0x10d29c0} <nil>
2.4 执行SQL语句
// 只执行,不返还结果集,返回执行 Result影响的行数和最后一条id
func (db *DB) Exec(query string, args ...interface{}) (Result, error) {}
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error) {}
// 返回大于0条结果集
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {}
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {}
// 预期结果集只有一行,没有结果集Scan时报ErrNoRows,Scan结果如果有多行,只取第一行,多余的数据行丢弃
func (db *DB) QueryRow(query string, args ...interface{}) *Row {}
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row {}
2.4.1返回结果集方面差别
表列 A | Exec() | Query() | QueryRow() |
---|---|---|---|
结果集 | 否 | 多条 | 1条 |
2.4.2 是否请求的时候带上下文Context
是 | 否 |
---|---|
ExecContext() | Exec() |
QueryContext() | Query() |
QueryRowContext() | QueryRow() |
超时查询的例子
在做数据库查询时,需要对数据的查询做超时控制,例如:
ctx = context.WithTimeout(context.Background(), time.Second)
rows, err := pool.QueryContext(ctx, "select * from products where id = ?", 100)
上面的代码基于 Background 派生出一个带有超时取消功能的ctx,传入带有context查询的方法中,如果超过1s未返回结果,则取消本次的查询。使用起来非常方便。
2.4.3 args参数
我们可以发现,每个函数参数都是支持可变参数列表,用法和prepare用法一样,用 ? 做占位符,那我们直接拼好sql和使用占位符哪种更优呢?
rows1, err := db.Query("select * from user where name = hisheng”)
rows2, err := db.Query("select * from user where a = ?", "hisheng")
这两条sql执行的结果是一样的,但是底层是不一样的,与不同驱动的具体实现略有差别。
以mysql为例,区别在于第一个Query,实际发送了一条sql(sql_type:3),第二条Query,实际发送了两条sql(sql_type:22 和 sql_tyep:23),先prepare,再execute,虽说二进制协议要快些,但是每次都会发送两条sql,第一次发送的prepare,之后只会execute一次且不会主动回收这个prepare信息。
这个接口设计之初,应该就是按照prepare+execute的思想设计的,当占位符参数个数为0时,能否优化直接发送一条sql,要看底层的驱动接口是否支持,换言之,prepare+execute
2.4.4 Exec(),不返还结果集,用的场景主要是非select的场景。
比如:
- 创建数据库,
- 创建表,
- 修改表,
- insert数据,
- update数据,
- delete数据等
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"testing"
"time"
)
func TestOpen(t *testing.T) {
//1 获取sql.DB
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database?charset=utf8")
t.Log(db, err)
//2 设置连接参数
db.SetMaxIdleConns(10) //最大空闲连接数
db.SetMaxOpenConns(11) //最大连接数
db.SetConnMaxLifetime(5 * time.Second) //最大连接时间
//3 访问数据库
err = db.Ping()
//4 执行sql
rs, err := db.Exec("" +
"CREATE TABLE `database`.`user` (" +
" `id` INT NOT NULL AUTO_INCREMENT, " +
" `name` VARCHAR(45) NULL, PRIMARY KEY (`id`)" +
");")
lid, err := rs.LastInsertId() //0 ,nil
t.Log(lid, err)
rc, err := rs.RowsAffected() //0 ,nil
t.Log(rc, err)
}
2.4.5 QueryRow 单条结果集
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"testing"
"time"
)
func TestOpen(t *testing.T) {
//1 获取sql.DB
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database?charset=utf8")
t.Log(db, err)
//2 设置连接参数
db.SetMaxIdleConns(10) //最大空闲连接数
db.SetMaxOpenConns(11) //最大连接数
db.SetConnMaxLifetime(5 * time.Second) //最大连接时间
//3 访问数据库
err = db.Ping()
//4 执行sql
row := db.QueryRow("select * from database.user where id = ?", 1)
t.Log(row, err)
type User struct {
Id int64
name string
}
var user User
err = row.Scan(&user.Id, &user.name)
t.Log(user, err)
}
user输出:
{1 hisheng} <nil>
2.4.6 Query 多条结果集
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"testing"
"time"
)
func TestOpen(t *testing.T) {
//1 获取sql.DB
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database?charset=utf8")
t.Log(db, err)
//2 设置连接参数
db.SetMaxIdleConns(10) //最大空闲连接数
db.SetMaxOpenConns(11) //最大连接数
db.SetConnMaxLifetime(5 * time.Second) //最大连接时间
//3 访问数据库
err = db.Ping()
//4 执行sql
rows, err := db.Query("select * from database.user")
defer rows.Close()
t.Log(rows, err)
type User struct {
Id int64
name string
}
var users []User
for rows.Next() {
var user User
err = rows.Scan(&user.Id, &user.name)
users = append(users, user)
}
t.Log(users, err)
}
users结果集输出:
[{1 hisheng} {2 haha}] <nil>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。