database/sql数据库驱动

接口定义

Go没有内置的驱动支持任何的数据库,而是定义了database/sql接口,要用户基于驱动接口开发相应数据库的驱动。GitHub上有许多基于database/sql接口开发的不同数据库驱动,基于Go提供的接口标准来开发的驱动的好处就是当你要换驱动时,只需要导入相应的驱动,改一下打开的driverName和dataSourceName即可,其它的基本不需要动。MySql驱动比如github.com/go-sql-driver/mysql

其它数据库及驱动(来自astaxie/build-web-application-with-golang)

MySql
https://github.com/go-sql-driver/mysql 支持database/sql,全部采用go写。
https://github.com/ziutek/mymysql 支持database/sql,也支持自定义的接口,全部采用go写。
https://github.com/Philio/GoMySQL 不支持database/sql,自定义接口,全部采用go写。

SQLite
https://github.com/mattn/go-sqlite3支持database/sql接口,基于cgo写的
https://github.com/feyeleanor/gosqlite3 不支持database/sql接口,基于cgo写的

https://github.com/phf/go-sqlite3 不支持database/sql接口,基于cgo写的

PostgreSQL
https://github.com/lib/pq 支持database/sql驱动,纯Go写的
https://github.com/jbarham/gopgsqldriver 支持database/sql驱动,纯Go写的

https://github.com/lxn/go-pgsql 支持database/sql驱动,纯Go写的

NOSQL数据库

redis
redis是一个key-value存储系统
https://github.com/garyburd/redigo (推荐)
https://github.com/go-redis/redis
https://github.com/hoisie/redis
https://github.com/alphazero/Go-Redis

https://github.com/simonz05/godis

mongoDB
MongoDB是一个高性能,开源,无模式的文档型数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
http://labix.org/mgo

在database/sql内部是通过map来存储不同驱动的,只需要调用sql.Register(name, 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驱动后,调用以下函数来注册驱动

func init() {
	sql.Register("mysql", &MySQLDriver{})//注册MySQL驱动
}

注册MySQL驱动是在init函数中,所以使用时需要导包,让其执行init方法,但又不使用,所以导入方式为import _ "github.com/go-sql-driver/mysql"

driver.Driver

driver.Driver是Go定义的一个驱动接口,只有一个Open方法,返回一个Conn接口

type Driver interface {
	Open(name string) (Conn, error)
}

MySQL驱动type MySQLDriver struct{}实现了driver.Driver接口返回了Conn接口,而MySQL的Conn的实现类型为mysql.mysqlContext

driver.Conn

Conn是一个数据库连接的接口定义,Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面。

sql定义的接口
type Conn interface {
	//返回与当前相关的执行sql语句的准备状态
	Prepare(query string) (Stmt, error) 
	//关闭当前的连接
	Close() error  
	//返回一个事务处理的Tx
	Begin() (Tx, error)
}
MySQL实现类型
type mysqlConn struct {
	buf              buffer
	netConn          net.Conn
	affectedRows     uint64
	insertId         uint64
	cfg              *Config
	maxAllowedPacket int
	maxWriteSize     int
	writeTimeout     time.Duration
	flags            clientFlag
	status           statusFlag
	sequence         uint8
	parseTime        bool

	// for context support (Go 1.8+)
	watching bool
	watcher  chan<- mysqlContext
	closech  chan struct{}
	finished chan<- struct{}
	canceled atomicError // set non-nil if conn is canceled
	closed   atomicBool  // set when conn is closed, before closech is closed
}

driver.Stmt

Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。

sql定义的接口
type Stmt interface {
	//关闭当前的链接状态
	Close() error
	//返回当前预留参数的个数
	NumInput() int
	//执行Prepare准备好的sql
	Exec(args []Value) (Result, error)
	//执行Prepare准备好的sql
	Query(args []Value) (Rows, error)
}
MySQL实现类型
type mysqlStmt struct {
	mc         *mysqlConn
	id         uint32
	paramCount int
}

driver.Tx

事务处理一般就两个过程,递交或者回滚。

sql定义的接口
type Tx interface {
	//递交事务
	Commit() error
	//回滚事务
	Rollback() error
}
MySQL实现类型
type mysqlTx struct {
	mc *mysqlConn
}

driver.Result

这个是执行Update/Insert/Delete操作返回的结果接口定义

sql定义的接口
type Result interface {
	//返回由数据库执行插入操作得到的自增ID号
	LastInsertId() (int64, error)
	//返回Update/Insert/Delete操作影响的数据条目数
	RowsAffected() (int64, error)
}
MySQL实现类型
type mysqlResult struct {
	affectedRows int64
	insertId     int64
}

driver.Rows

Rows是执行查询返回的结果集接口定义

sql定义的接口
type Rows interface {
	//返回查询数据库表的字段信息
	Columns() []string
	//关闭Rows迭代器
	Close() error
	//返回下一条数据,把数据赋值给dest
	Next(dest []Value) error
}
MySQL实现类型
type binaryRows struct {
	mysqlRows
}

database/sql

database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn pool。

type DB struct {
	connector driver.Connector

	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

	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{}

	stop func() // stop cancels the connection opener and the session resetter.
}

我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行db.prepare -> db.prepareDC的时候会defer dc.releaseConn,然后调用db.putConn,也就是把这个连接放入连接池,每次调用db.conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。

使用

使用github.com/go-sql-driver/mysql驱动

导包

import (
	"database/sql"
	"log"
	"time"
	_ "github.com/go-sql-driver/mysql"
	"fmt"
)

初始化

注意:要想解析time.Time类型,必须要设置parseTime=True

var DB *sql.DB
func init() {
	var err error
	DB,err = sql.Open("mysql","root:root@tcp(localhost:3306)/cgo?charset=utf8&parseTime=True&loc=Local")
	if err != nil{
		log.Panic(err)
	}
	DB.SetMaxOpenConns(30)
	DB.SetMaxIdleConns(10)
	err = DB.Ping()
	if err != nil{
		log.Panic(err)
	}
}

插入数据

func insert() int64 {
	result,err := DB.Exec("INSERT INTO user(`username`,`password`,`create_time`) VALUES (?,?,?)","user_1234","1234",time.Now())
	if err != nil{
		log.Panic(err)
	}
	id,err := result.LastInsertId()
	if err != nil{
		log.Panic(err)
	}
	return id
}

插入数据方式2

func insert2() int64 {
	stmt,err := DB.Prepare("INSERT INTO user(`username`,`password`,`create_time`) VALUES(?,?,?)")
	if err != nil {
		log.Panic(err)
	}
	result,err := stmt.Exec("user_1111","1111",time.Now())
	if err != nil{
		log.Panic(err)
	}
	id,err := result.LastInsertId()
	if err != nil{
		log.Panic(err)
	}
	fmt.Println("id",id)
	num,err := result.RowsAffected()
	if err != nil{
		log.Panic(err)
	}
	fmt.Println("num",num)
        stmt.Close()
	return id
}

更新数据

func update() int64{
	result,err := DB.Exec("UPDATE user SET `password` = ? WHERE id = ?","5678",3)
	if err != nil {
		log.Panic(err)
	}
	num,err := result.RowsAffected()
	if err != nil{
		log.Panic(err)
	}
	return num
}

删除数据

func delete() int64 {
	result,err := DB.Exec("DELETE FROM user WHERE id = ?",3)
	if err != nil{
		log.Println(err)
	}
	num,err := result.RowsAffected()
	if err != nil{
		log.Panic(err)
	}
	return num
}

查询单条数据

type MUser struct {
	ID int64
	Username string
	Password string
	CreateTime time.Time
}

func query() *MUser {
	var user MUser
	err := DB.QueryRow("SELECT * FROM user WHERE id = ?",3).Scan(&user.ID,&user.Username,&user.Password,&user.CreateTime)
	if err != nil {
		//如果查询不到结果报错panic: sql: no rows in result set
		log.Panic(err)
	}
	return &user
}

查询多条数据

func querys() []*MUser {
	var users []*MUser
	rows,err := DB.Query("SELECT * FROM user")
	if err != nil{
		log.Panic(err)
	}
	for rows.Next(){
		var user MUser
		err := rows.Scan(&user.ID,&user.Username,&user.Password,&user.CreateTime)
		if err != nil {
			continue
		}
		users = append(users,&user)
	}
        rows.Close()
	return users
}

事务

func transaction() {
	tx,err := DB.Begin()
	if err != nil{
		log.Panic(err)
	}
	result,err := tx.Exec("INSERT INTO user(`username`,`password`,`create_time`) VALUES(?,?,?)","user_1234","1234",time.Now())
	if err != nil{
		log.Panic(err)
	}
	id,err := result.LastInsertId()
	if err != nil{
		log.Panic(err)
	}
	result,err = tx.Exec("UPDATE user SET `password` = ? WHERE id = ?","5678",id)
	if err != nil{
		tx.Rollback()
		log.Panic(err)
	}
	id,err = result.LastInsertId()
	if err != nil{
		log.Panic(err)
	}
	fmt.Println("id",id)
	num,err := result.RowsAffected()
	if err != nil{
		log.Panic(err)
	}
	fmt.Println("num",num)
	tx.Commit()
}

参考:https://github.com/astaxie/build-web-application-with-golang


版权声明:本文为cj_286原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。