go标准库的学习-database/sql

参考:https://studygolang.com/pkgdochtml

导入方式:mysql

import "database/sql"

sql包提供了保证SQL或类SQL数据库的泛用接口。git

使用sql包时必须注入(至少)一个数据库驱动。相关可见go标准库的学习-database/sql/drivergithub

 

1.空值golang

参考:https://yq.aliyun.com/articles/178898?utm_content=m_29337sql

当用户认为数据库中的某一列不会出现空值(即nil)而将该列设置为基本类型,而后在从数据库中接收写入数据时若是获得了空值nil,程序就会崩溃。数据库

⚠️空值(即nil)和零值是不一样的,Go语言的每个变量都有着默认零值,当数据的零值没有意义时,能够用零值来表示空值。安全

空值的解决办法有:socket

1)使用零值tcp

若是数据自己从语义上就不会出现零值,或者根本不区分零值和空值,那么最简便的方法就是使用零值来表示空值

2)数据库层面解决办法

经过对列添加NOT NULL约束,能够确保任何结果都不会为空。或者,经过在SQL中使用COALESCE来为NULL设定默认值。

3)自定义处理逻辑,以下

type Scanner

type Scanner interface {
    // Scan方法从数据库驱动获取一个值。
    //
    // 参数src的类型保证为以下类型之一:
    //
    //    int64
    //    float64
    //    bool
    //    []byte
    //    string
    //    time.Time
    //    nil - 表示NULL值
    //
    // 若是不能不丢失信息的保存一个值,应返回错误。
    Scan(src interface{}) error
}

Scanner接口会被Rows或Row等的Scan方法使用。

任何实现了Scanner接口的类型,均可以经过定义本身的Scan函数来处理空值问题,好比:

  • Rows或Row的Scan方法其实就是实现从数据库驱动中获取一个值(该值会转换成src的类型),并将其存储到src,src知足driver.Value类型
  • 而NullString、NullBool等的Scan方法则是将输入的值转换成对应的NullString类型并存储下来

4)使用额外的标记字段,以下面的 Valid字段

database\sql提供了四种基本可空数据类型:使用基本类型和一个布尔标记的复合结构体表示可空值

type NullString

type NullString struct {
    String string
    Valid  bool // 若是String不是NULL则Valid为真
}

NullString表明一个可为NULL的字符串。NullString实现了Scanner接口,所以能够做为Rows/Row的Scan方法的参数保存扫描结果:

var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s) //即将获得的name值转换成s.String类型并存储到&s中
...
if s.Valid {//若是name值非空值
   // use s.String
} else {//若是name值为空值
   // NULL value
}

func (*NullString) Scan

func (ns *NullString) Scan(value interface{}) error

Scan实现了Scanner接口。

func (NullString) Value

func (ns NullString) Value() (driver.Value, error)

Value实现了driver.Valuer接口。

其实现源码为:

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

// Scan implements the Scanner interface.
func (ns *NullString) Scan(value interface{}) error {
    if value == nil { //若是
        ns.String, ns.Valid = "", false
        return nil
    }
    ns.Valid = true
    return convertAssign(&ns.String, value)
}

// Value implements the driver Valuer interface.
func (ns NullString) Value() (driver.Value, error) {
    if !ns.Valid {
        return nil, nil
    }
    return ns.String, nil
}

其中使用到了一个函数convertAssign:

//该函数的做用是将src的值复制到dest上,并将src的类型转换成dest的类型,可转换则返回nil;不然返回错误
//dest是一个指针类型
func convertAssign(dest, src interface{}) error

所以上面的Scan函数的做用就是将非nil的传入参数值value转换成ns.String类型,并存储在&ns.String中,同时设置ns.Valid为true

type NullBool

type NullBool struct {
    Bool  bool
    Valid bool // 若是Bool不是NULL则Valid为真
}

NullBool表明一个可为NULL的布尔值。NullBool实现了Scanner接口,所以能够做为Rows/Row的Scan方法的参数保存扫描结果,相似NullString。

func (*NullBool) Scan

func (n *NullBool) Scan(value interface{}) error

Scan实现了Scanner接口。

func (NullBool) Value

func (n NullBool) Value() (driver.Value, error)

Value实现了driver.Valuer接口。

type NullInt64

type NullInt64 struct {
    Int64 int64
    Valid bool // 若是Int64不是NULL则Valid为真
}

NullInt64表明一个可为NULL的int64值。NullInt64实现了Scanner接口,所以能够做为Rows/Row的Scan方法的参数保存扫描结果,相似NullString。

func (*NullInt64) Scan

func (n *NullInt64) Scan(value interface{}) error

Scan实现了Scanner接口。

func (NullInt64) Value

func (n NullInt64) Value() (driver.Value, error)

Value实现了driver.Valuer接口。

type NullFloat64

type NullFloat64 struct {
    Float64 float64
    Valid   bool // 若是Float64不是NULL则Valid为真
}

NullFloat64表明一个可为NULL的float64值。NullFloat64实现了Scanner接口,所以能够做为Rows/Row的Scan方法的参数保存扫描结果,相似NullString。

func (*NullFloat64) Scan

func (n *NullFloat64) Scan(value interface{}) error

Scan实现了Scanner接口。

func (NullFloat64) Value

func (n NullFloat64) Value() (driver.Value, error)

Value实现了driver.Valuer接口。

 

type RawBytes

type RawBytes []byte

RawBytes是一个字节切片,保管对内存的引用,为数据库自身所使用。在Scaner接口的Scan方法写入RawBytes数据后,该切片只在限次调用Next、Scan或Close方法以前合法。

 

2.DB

1)func Register

func Register(name string, driver driver.Driver)

Register注册并命名一个数据库,能够在Open函数中使用该命名启用该驱动。

若是 Register注册同一名称两次,或者driver参数为nil,会致使panic。

该函数用来注册数据库驱动。当第三方开发者开发数据库驱动时,都会实现init函数,在init里面调用这个Register(name string, driver driver.Driver)完成本驱动的注册,好比

1>sqlite3的驱动:

//http://github.com/mattn/go-sqlite3驱动
func init(){
    sql.Register("sqlite3", &SQLiteDriver{})
}

2>mysql的驱动

//http://github.com/mikespook/mymysql驱动
var d = Driver{proto : "tcp", raddr : "127.0.0.1:3306"}
func init(){
    Register("SET NAMES utf8")
    sql.Register("mymysql", &d)
}

由上可见第三方数据库驱动都是经过这个函数来注册本身的数据库驱动名称及相应的driver实现。

上面的例子实现的都是注册一个驱动,该函数还可以实现同时注册多个数据库驱动,只要这些驱动不重复,经过一个map来存储用户定义的相应驱动

var drivers = make(map[string]driver.Driver) drivers[name] = driver

 

在使用database/sql接口和第三方库时常常看见以下:

import(
    "database/sql" _ "github.com/mattn/go-sqlite3" //上面定义的sqlite3驱动包 )

里面的_的做用就是说明引入了"github.com/mattn/go-sqlite3"该包,可是不直接使用包里面的函数或变量,会先调用包中的init函数。这种使用方式仅让导入的包作初始化,而不使用包中其余功能

 

2)type DB

type DB struct {
    // 内含隐藏或非导出字段
}

DB是一个数据库(操做)句柄,表明一个具备零到多个底层链接的链接池。它能够安全的被多个go程同时使用。

sql.DB不是一个链接,它是数据库的抽象接口。它能够根据driver驱动打开关闭数据库链接,管理链接池。正在使用的链接被标记为繁忙,用完后回到链接池等待下次使用。因此,若是你没有把链接释放回链接池,会致使过多链接使系统资源耗尽。

sql包会自动建立和释放链接;它也会维护一个闲置链接的链接池。若是数据库具备单链接状态的概念,该状态只有在事务中被观察时才可信。

一旦调用了DB.Begin,返回的Tx会绑定到单个链接。当调用事务Tx的Commit或Rollback后,该事务使用的链接会归还到DB的闲置链接池中。

链接池的大小能够用SetMaxIdleConns方法控制。

func Open

func Open(driverName, dataSourceName string) (*DB, error)

Open打开一个dirverName指定的数据库,dataSourceName指定数据源,通常包至少括数据库文件名和(可能的)链接信息。

大多数用户会经过数据库特定的链接帮助函数打开数据库,返回一个*DB。Go标准库中没有数据库驱动。参见http://golang.org/s/sqldrivers获取第三方驱动。

 Open函数不建立与数据库的链接,也不验证其参数。它可能会延迟到你第一次调用该数据库时才回去真正建立与数据库的链接。因此若是要当即检查数据源的名称是否合法,或者数据库是否实际可用,应调用返回值的Ping方法。

func (*DB) Ping

func (db *DB) Ping() error

Ping检查与数据库的链接是否仍有效,若是须要会建立链接。

func (*DB) Close

func (db *DB) Close() error

Close关闭数据库,释听任何打开的资源。通常不会关闭DB,由于DB句柄一般被多个go程共享,并长期活跃。

 

举例,正确是不会报错:

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

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    if err != nil{
        panic(err)
    }

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }
}

若是写错密码,则会返回:

userdeMBP:go-learning user$ go run test.go
2019/02/20 19:51:00 Error 1045: Access denied for user 'root'@'localhost' (using password: YES)
exit status 1

可见调用sql.Open()函数时并无报错,是调用db.Ping()函数时才报出的密码错误

 

返回的DB能够安全的被多个go程同时使用,并会维护自身的闲置链接池。这样一来,Open函数只需调用一次。不多须要关闭DB,由于sql.DB对象是为了长链接设计的,不要频繁使用Open()和Close()函数,不然会致使各类错误

所以应该为每一个待访问的数据库建立一个sql.DB实例,并在用完前保留它。若是须要短链接使用,那么能够将其做为函数的参数传递给别的function的参数使用,而不是在这个function中调用Open()和Close()再创建已经建立的sql.DB实例,或者将其设置为全局变量。

  • 第一个参数是调用的驱动名,好比下面的例子中使用的是github.com/go-sql-driver/mysql中注册的驱动"mysql"
  • 第二个参数依赖与特定驱动的语法,用来链接数据库,一般是URL的形式,如"root:user78@/test"

func (*DB) Driver

func (db *DB) Driver() driver.Driver

Driver方法返回数据库下层驱动。

 

下面的四个函数用于进行数据库操做:

func (*DB) Exec

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Exec执行一次命令(包括查询、删除、更新、插入等),不返回数据集,返回的结果是Result,Result接口容许获取执行结果的元数据。参数args表示query中的占位参数。

func (*DB) Query

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Query执行一次查询,返回多行结果(即Rows),通常用于执行select命令。参数args表示query中的占位参数。

上面两个的差异在与:Query会返回查询结果Rows,Exec不会返回查询结果,只会返回一个结果的状态Result

因此通常进行不须要返回值的DDL和增删改等操做时会使用Exec,查询则使用Query。固然这主要仍是取决因而否须要返回值

func (*DB) QueryRow

func (db *DB) QueryRow(query string, args ...interface{}) *Row

QueryRow执行一次查询,并指望返回最多一行结果(即Row)。QueryRow老是返回非nil的值,直到返回值的Scan方法被调用时,才会返回被延迟的错误。(如:未找到结果)

func (*DB) Prepare

func (db *DB) Prepare(query string) (*Stmt, error)

Prepare建立一个准备好的状态用于以后的查询和命令,即准备一个须要屡次使用的语句,供后续执行用。返回值能够同时执行多个查询和命令。

 

func (*DB) Begin

func (db *DB) Begin() (*Tx, error)

Begin开始一个事务。隔离水平由数据库驱动决定。

 

举一个简单例子:

首先先在mysql中建立数据库test,并生成两个表,一个是用户表userinfo,一个是关联用户信息表userdetail。使用workbench进行建立,首先建立数据库test:

CREATE SCHEMA `test` DEFAULT CHARACTER SET utf8 ;

而后建立表:

use test;
create table `userinfo` (
    `uid` int(10) not null auto_increment, `username` varchar(64) null default null, `department` varchar(64) null default null, `created` date null default null, primary key (`uid`) ); create table `userdetail`( `uid` int(10) not null default '0', `intro` text null, `profile` text null, primary key (`uid`) );

接下来就示范怎么使用database/sql接口对数据库进行增删改查操做:

固然运行前首先须要下载驱动:

go get -u github.com/go-sql-driver/mysql

固然,若是你链接的是sqlite3数据库,那么你要下载的驱动是:

http://github.com/mattn/go-sqlite3

举例;

package main 
import(
    "fmt" "database/sql" _ "github.com/go-sql-driver/mysql" ) func checkErr(err error){ if err != nil{ panic(err) } } func main() { db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"  defer db.Close() checkErr(err) //插入数据 stmt, err := db.Prepare("insert userinfo set username = ?,department=?,created=?") checkErr(err) //执行准备好的Stmt res, err := stmt.Exec("user1", "computing", "2019-02-20") checkErr(err) //获取上一个,即上面insert操做的ID id, err := res.LastInsertId() checkErr(err) fmt.Println(id) //1 //更新数据 stmt, err =db.Prepare("update userinfo set username=? where uid=?") checkErr(err) res, err = stmt.Exec("user1update", id) checkErr(err) affect, err := res.RowsAffected() checkErr(err) fmt.Println(affect) //1 //查询数据 rows, err := db.Query("select * from userinfo") checkErr(err) for rows.Next() { //做为循环条件来迭代获取结果集Rows 
     //从结果集中获取一行结果 err = rows.Scan(&uid, &username, &department, &created) //1 user1update computing 2019-02-20 checkErr(err) fmt.Println(uid, username, department, created) }   defer rows.Close() //关闭结果集,释放连接 //删除数据 stmt, err = db.Prepare("delete from userinfo where uid=?") checkErr(err) res, err = stmt.Exec(id) checkErr(err) affect, err = res.RowsAffected() checkErr(err) fmt.Println(affect) //1 }

返回:

userdeMBP:go-learning user$ go run test.go
1 1 1 user1update computing 2019-02-20 1

上面代码使用的函数的做用分别是:

1.sql.Open()函数用来打开一个注册过的数据库驱动,go-sql-driver/mysql中注册了mysql这个数据库驱动,第二个参数是DNS(Data Source Name),它是go-sql-driver/mysql定义的一些数据库链接和配置信息,其支持下面的几种格式:

user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8 user:password@/dbname user:password@tcp([de:ad:be::ca:fe]:80)/dbname

2.db.Prepare()函数用来返回准备要执行的sql操做,而后返回准备完毕的执行状态

3.db.Query()函数用来直接执行Sql并返回Rows结果

4.stmt.Exec()函数用来执行stmt准备好的SQL语句,而后返回Result

⚠️sql中传入的参数都是=?对应的数据,这样作能够在必定程度上防止SQL注入

 

type Result

type Result interface {
    // LastInsertId返回一个数据库生成的回应命令的整数。
    // 当插入新行时,通常来自一个"自增"列。
    // 不是全部的数据库都支持该功能,该状态的语法也各有不一样。
    LastInsertId() (int64, error)

    // RowsAffected返回被update、insert或delete命令影响的行数。
    // 不是全部的数据库都支持该功能。
    RowsAffected() (int64, error)
}

Result是对已执行的SQL命令的总结。

 

func (*DB) SetMaxOpenConns

func (db *DB) SetMaxOpenConns(n int)

SetMaxOpenConns设置与数据库创建链接的最大数目。

若是n大于0且小于最大闲置链接数,会将最大闲置链接数减少到匹配最大开启链接数的限制。

若是n <= 0,不会限制最大开启链接数,默认为0(无限制)。

func (*DB) SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

SetMaxIdleConns设置链接池中的最大闲置链接数。

若是n大于最大开启链接数,则新的最大闲置链接数会减少到匹配最大开启链接数的限制。

若是n <= 0,不会保留闲置链接。

 

3.Row

上面的DB的函数Query()和QueryRow()会返回*ROWs和*ROW,所以下面就是如何去获得返回结果的更多详细的信息

1)type Row

type Row struct {
    // 内含隐藏或非导出字段
}

QueryRow方法返回Row,表明单行查询结果。

func (*Row) Scan

func (r *Row) Scan(dest ...interface{}) error

Scan将该行查询结果各列分别保存进dest参数指定的值中。若是该查询匹配多行,Scan会使用第一行结果并丢弃其他各行。若是没有匹配查询的行,Scan会返回ErrNoRows。

举例:

一开始数据库中为空,所以调用Scan会返回错误:

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

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    if err != nil{
        panic(err)
    }

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }

    //查询数据
    var uid int 
    var username, department, created string
    err = db.QueryRow("select * from userinfo").Scan(&uid, &username, &department, &created)
    switch {
    case err == sql.ErrNoRows:
        log.Printf("No user with that ID.") //返回 2019/02/21 10:38:33 No user with that ID.
    case err != nil:
        log.Fatal(err)
    default:
        fmt.Printf("Username is %s\n", username)
    }

}

所以若是先插入数据再调用QueryRow则不会出错了:

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

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    if err != nil{
        log.Fatal(err)
    }

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }

    stmt, err := db.Prepare("insert userinfo set username =?,department=?,created=?")
    if err != nil{
        log.Fatal(err)
    }

    _, err = stmt.Exec("testQueryRow", "computing", "2019-02-21")
    if err != nil{
        log.Fatal(err)
    }

    //查询数据
    var uid int 
    var username, department, created string
    err = db.QueryRow("select * from userinfo").Scan(&uid, &username, &department, &created)
    switch {
    case err == sql.ErrNoRows:
        log.Printf("No user with that ID.")
    case err != nil:
        log.Fatal(err)
    default:
        fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created)
    }

}

返回:

userdeMBP:go-learning user$ go run test.go
Uid is 3, username is testQueryRow, department is computing, created at 2019-02-21

 

2)type Rows

type Rows struct {
    // 内含隐藏或非导出字段
}

Rows是查询的结果。它的游标指向结果集的第零行,使用Next方法来遍历各行结果:

rows, err := db.Query("SELECT ...")
...
defer rows.Close()
for rows.Next() {
    var id int
    var name string
    err = rows.Scan(&id, &name)
    ...
}
err = rows.Err() // 在退出迭代后检查错误
...

func (*Rows) Columns

func (rs *Rows) Columns() ([]string, error)

Columns返回列名。若是Rows已经关闭会返回错误。

func (*Rows) Scan

func (rs *Rows) Scan(dest ...interface{}) error

Scan将当前行各列结果填充进dest指定的各个值中,用于在迭代中获取一行结果。

若是某个参数的类型为*[]byte,Scan会保存对应数据的拷贝,该拷贝为调用者全部,能够安全的,修改或无限期的保存。若是参数类型为*RawBytes能够避免拷贝;参见RawBytes的文档获取其使用的约束。

若是某个参数的类型为*interface{},Scan会不作转换的拷贝底层驱动提供的值。若是值的类型为[]byte,会进行数据的拷贝,调用者能够安全使用该值。

func (*Rows) Next

func (rs *Rows) Next() bool

Next准备用于Scan方法的下一行结果。若是成功会返回true,若是没有下一行或者出现错误会返回false。Err()方法应该被调用以区分这两种状况。

每一次调用Scan方法,甚至包括第一次调用该方法,都必须在前面先调用Next方法。

func (*Rows) Close

func (rs *Rows) Close() error

Close关闭Rows,阻止对其更多的列举。 若是Next方法返回false,Rows会自动关闭,知足检查Err方法结果的条件。Close方法是幂等的(即屡次调用不会出错),不影响Err方法的结果。

用于关闭结果集Rows。结果集引用了数据库链接,并会从中读取结果。读取完以后必须关闭它才能避免资源泄露。只要结果集仍然打开着,相应的底层链接就处于忙碌状态,不能被其余查询使用。

func (*Rows) Err

func (rs *Rows) Err() error

Err返回可能的、在迭代时出现的错误,即用于在退出迭代后检查错误。Err需在显式或隐式调用Close方法后调用,即若是Next方法返回false,Rows会自动关闭,至关于调用了Close()。

正常状况下迭代退出是由于内部产生的EOF错误(即数据读取完毕),使得下一次rows.Next() == false,从而终止循环;在迭代结束后要检查错误,以确保迭代是由于数据读取完毕,而非其余“真正”错误而结束的。

举例:

包括上面的例子,这里再插入一条数据,这样数据库中就有两条数据了

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

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    if err != nil{
        log.Fatal(err)
    }

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }

    stmt, err := db.Prepare("insert userinfo set username =?,department=?,created=?")
    if err != nil{
        log.Fatal(err)
    }

    _, err = stmt.Exec("testQuery", "data mining", "2019-02-21")
    if err != nil{
        log.Fatal(err)
    }

    //查询数据
    rows, err := db.Query("select * from userinfo")
    if err != nil{
        log.Fatal(err)
    }
    defer rows.Close()

    //迭代结果
    var uid int 
    var username, department, created string
    for rows.Next() {
        if err = rows.Scan(&uid, &username, &department, &created); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created)
    }
    //查看迭代时是否出错以及出的是什么错
    if rows.Err() != nil {
        log.Fatal(err)
    }

}

返回:

userdeMBP:go-learning user$ go run test.go
Uid is 3, username is testQueryRow, department is computing, created at 2019-02-21
Uid is 4, username is testQuery, department is data mining, created at 2019-02-21

 

4.Stmt

在调用db.Prepare()后会返回*Stmt,即准备好的语句,通常一个会屡次进行查询的语句就应该将其设置为准备好的语句。

Stmt是和单个数据库直接绑定的。客户端会发送一个带有占位符,如?的SQL语句的Stmt到服务端,而后服务端会返回一个Stmt ID,说明给你绑定的链接是哪个。而后以后当客户端要执行该Stmt时,就会发送ID和参数来绑定链接并执行操做。

要注意的是不能直接为Stmt绑定链接,链接只能与DB和Tx绑定,当咱们生成一个Stmt时,首先它会自动在链接池中绑定一个空闲链接,而后Stmt会记住该链接,而后以后执行时尝试使用这个链接,若是不可用,如链接繁忙或关闭,则会从新准备语句并再绑定一个新的链接

Stmt中能够执行的方法与db中的方法十分相似

type Stmt

type Stmt struct {
    // 内含隐藏或非导出字段
}

Stmt是准备好的状态。Stmt能够安全的被多个go程同时使用。

func (*Stmt) Exec

func (s *Stmt) Exec(args ...interface{}) (Result, error)

Exec使用提供的参数执行准备好的命令状态,返回Result类型的该状态执行结果的总结。

func (*Stmt) Query

func (s *Stmt) Query(args ...interface{}) (*Rows, error)

Query使用提供的参数执行准备好的查询状态,返回Rows类型查询结果。

func (*Stmt) QueryRow

func (s *Stmt) QueryRow(args ...interface{}) *Row

QueryRow使用提供的参数执行准备好的查询状态。若是在执行时遇到了错误,该错误会被延迟,直到返回值的Scan方法被调用时才释放。返回值老是非nil的。若是没有查询到结果,*Row类型返回值的Scan方法会返回ErrNoRows;不然,Scan方法会扫描结果第一行并丢弃其他行。

示例用法:

var name string
err := nameByUseridStmt.QueryRow(id).Scan(&name)

func (*Stmt) Close

func (s *Stmt) Close() error

Close关闭状态。

举例:

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

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    if err != nil{
        log.Fatal(err)
    }

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }

    stmt1, err := db.Prepare("insert userinfo set username =?,department=?,created=?")
    if err != nil{
        log.Fatal(err)
    }

    _, err = stmt1.Exec("testStmtExecAndQueryRow", "accounting", "2019-02-21")
    if err != nil{
        log.Fatal(err)
    }
    defer stmt1.Close()

    stmt2, err := db.Prepare("select * from userinfo where uid =?")
    if err != nil{
        log.Fatal(err)
    }

    //查询数据
    var uid int 
    var username, department, created string
    err = stmt2.QueryRow(5).Scan(&uid, &username, &department, &created)
    if err != nil{
        log.Fatal(err)
    }
    fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created)
    defer stmt2.Close()

}

返回:

userdeMBP:go-learning user$ go run test.go
Uid is 5, username is testStmtExecAndQueryRow, department is accounting, created at 2019-02-21

 

5.Tx

db.Begin()函数会返回*Tx。Go中事务(Tx)是一个持有数据库链接的对象,它容许用户在同一个链接上执行上面提到的各种操做。

使用它的缘由是:Tx上执行的方法都保证是在同一个底层链接上执行的,止痒对链接状态的修改将会一直对后续的操做起做用

然而DB的方法就不会保证是在同一条链接上执行,若是以前的链接繁忙或关闭,那么就会使用其余的链接

⚠️Tx和Stmt不能分离,意思就是Tx必须调用本身的Tx.Prepare()函数来生成Stmt来供本身使用,而不能使用DB生成的Stmt,由于这样他们使用的一定不是同一个链接。

固然,若是你想要在该事务中使用已存在的状态,参见Tx.Stmt方法,将DB的Stmt转成Tx的Stmt。

type Tx

type Tx struct {
    // 内含隐藏或非导出字段
}

Tx表明一个进行中的数据库事务。

一次事务必须以对Commit或Rollback的调用结束。

调用Commit或Rollback后,全部对事务的操做都会失败并返回错误值ErrTxDone。

func (*Tx) Exec

func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)

Exec执行命令,但不返回结果。例如执行insert和update。

func (*Tx) Query

func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)

Query执行查询并返回零到多行结果(Rows),通常执行select命令。

func (*Tx) QueryRow

func (tx *Tx) QueryRow(query string, args ...interface{}) *Row

QueryRow执行查询并指望返回最多一行结果(Row)。QueryRow老是返回非nil的结果,查询失败的错误会延迟到在调用该结果的Scan方法时释放。

func (*Tx) Prepare

func (tx *Tx) Prepare(query string) (*Stmt, error)

Prepare准备一个专用于该事务的状态。返回的该事务专属状态操做在Tx递交或回滚后不能再使用,所以必定要在事务结束前,即调用Commit()或Rollback函数前关闭准备语句

在事务中使用defer stmt.Close()是至关危险的。由于当事务Stmt结束后,它会先释放本身持有的数据库DB链接,但事务Tx建立的未关闭Stmt仍然保留着对事务Tx链接的引用

在事务结束后执行stmt.Close(),他就会根据引用去查找以前的数据库DB链接,而后想要释放它。可是其实数据库的链接早就被释放了,并且若是原来释放的数据库DB链接已经被其余查询获取并使用,就会产生竞争,极有可能破坏链接的状态。所以二者的释放顺序是十分重要的

举例:

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

func checkErr(err error){
    if err != nil{
        log.Fatal(err)
    }  
}

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    checkErr(err)

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }
    tx, err := db.Begin()
    checkErr(err)
    defer tx.Commit()

    stmt1, err := tx.Prepare("insert userinfo set username =?,department=?,created=?")
    checkErr(err)

    result, err := stmt1.Exec("testTx", "PD", "2019-02-21")
    checkErr(err)
    id, err := result.LastInsertId()
    checkErr(err)
    defer stmt1.Close()

    stmt2, err := tx.Prepare("select * from userinfo where uid =?")
    checkErr(err)

    //查询数据
    var uid int 
    var username, department, created string
    err = stmt2.QueryRow(id).Scan(&uid, &username, &department, &created)
    checkErr(err)
    fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created)
    defer stmt2.Close()

}

上面的defer会安装stmt2 -> stmt1 -> tx -> db的顺序来关闭链接

成功返回:

userdeMBP:go-learning user$ go run test.go
Uid is 6, username is testTx, department is PD, created at 2019-02-21

若是将tx.Commit()写在stmt.Close()以前,则会出错,举例:

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

func checkErr(err error){
    if err != nil{
        log.Fatal(err)
    }  
}

func main() {
    db, err := sql.Open("mysql", "root:user78@/test") //后面格式为"user:password@/dbname"
    defer db.Close()
    checkErr(err)

    //使用Ping检查数据库是否实际可用
    if err = db.Ping(); err != nil{
        log.Fatal(err)
    }
    tx, err := db.Begin()
    checkErr(err)
    tx.Commit()

    stmt, err := tx.Prepare("select * from userinfo where uid =?")
    checkErr(err)

    //查询数据
    var uid int 
    var username, department, created string
    err = stmt.QueryRow(6).Scan(&uid, &username, &department, &created)
    checkErr(err)
    fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created)

    err = tx.Commit()
    checkErr(err)
    
    defer stmt.Close()

}

返回:

userdeMBP:go-learning user$ go run test.go
2019/02/21 15:58:00 sql: transaction has already been committed or rolled back
exit status 1

 

func (*Tx) Stmt

func (tx *Tx) Stmt(stmt *Stmt) *Stmt

Stmt使用已存在的状态生成一个该事务特定的状态。

示例:

updateMoney, err := db.Prepare("UPDATE balance SET money=money+? WHERE id=?")
...
tx, err := db.Begin()
...
res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)

func (*Tx) Commit

func (tx *Tx) Commit() error

Commit递交事务。

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Rollback放弃并回滚事务。

相关文章
相关标签/搜索