1.介绍

Go官网提供了database包,database包下有sql/driver。该包用来定义操作数据库的接口,这保障了无论应用哪种数据库,操作形式都是雷同的。但Go官网并没有提供连贯数据库的driver,如果要操作数据库,还须要第三方的driver包。

2.下载安装驱动

go-sql-driver驱动源码地址: https://github.com/go-sql-dri...

2.1 装置驱动

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

3.匿名导入

<font color=blue>通常来说,导入包后就能调用该包中的数据和办法。然而对于数据库操作来说,不应该间接应用导入驱动包所提供的办法,而应该应用 sql.DB对象所提供的对立的办法。因而在导入MySQL驱动时,应用了匿名导入包的形式。</font>

匿名导入包: 只导入包然而不应用包内的类型和数据,应用匿名的形式: 在包门路前增加下画线_
import (    _ "github.com/go-sql-driver/mysql")

在导入一个数据库驱动后,该驱动会自行初始化并注册到Godatabase/sql上下文中,这样就能够通过 database/sql 包所提供的办法来拜访数据库了。

4.连贯数据库

4.1 连贯办法

应用sql包中的Open()函数来连贯数据库。

Open(driverName, dataSourceName string) (*DB, error)
  • driverName: 应用的驱动名,如mysql。(注册到 database/sql时所应用的名字)
  • dataSourceName:数据库连贯信息,格局:[用户名:明码@tcp(IP:port)/数据库?charset=utf8],例如:root:123456@tcp(127.0.0.1:3306)/test?charset=utf8

4.2 sql.DB作用

  • sql.Open()返回的sql.DB对象是Goroutine并发平安的。
  • sql.DB 通过数据库驱动为开发者提供治理底层数据库连贯的关上和敞开操作。
  • sql.DB 帮忙开发者治理数据库连接池。正在应用的连贯被标记为忙碌,用完后回到连接池期待下次应用。所以,<font color=red>如果开发者没有把连贯开释回连接池,会导致过多连贯使系统资源耗尽。</font>

4.3 sql.DB设计指标

sql.DB的设计指标就是作为长连贯(一次连贯屡次数据交互)应用,不宜频繁开关。比拟好的做法是,为每个不同的datastore建一个DB对象,放弃这些对象关上。如果须要短连贯(一次连贯一次数据交互),就把DB作为参数传入function,而不要在function中开关。

5.写操作(增、删、改)

5.1 执行步骤

  1. 先应用预编译语句(PreparedStatement)来拼接sql
  2. 而后调用db.Exec()执行SQL,返回执行后果

5.2 代码示例

package mainimport (    "database/sql"    "fmt"    _ "github.com/go-sql-driver/mysql"    "time")func main() {    // 连贯数据库    open, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test?charset=utf8")    checkError(err)  //插入数据    //add(open)    // 更新数据    //update(open)    // 删除数据    del(open)}//插入数据func add(open *sql.DB)  {    //插入数据    prepare, err := open.Prepare("insert user set username=?,password=?,mobile=?,createtime=?")    checkError(err)    exec, err := prepare.Exec("李四", "123456", "17600000000", time.Now().Unix())    checkError(err)    id, err := exec.LastInsertId()    checkError(err)    fmt.Printf("插入数据ID: %d \n",id)}// 更新func update(open *sql.DB)  {    prepare, err := open.Prepare("update user set username=? where id =?")    checkError(err)    exec, err := prepare.Exec("王五", "18")    checkError(err)    rows, err := exec.RowsAffected()    checkError(err)    fmt.Printf("更新数据胜利,影响条数 %d \n",rows)}// 删除数据func del(open *sql.DB)  {    prepare, err := open.Prepare("delete from user  where id =?")    checkError(err)    exec, err := prepare.Exec( "8")    checkError(err)    rows, err := exec.RowsAffected()    checkError(err)    fmt.Printf("删除数据胜利,影响条数 %d \n",rows)}//检测谬误func checkError(err error)  {    if err != nil {        panic("操作失败:"+err.Error())    }}

6. 读操作(查问)

6.1 执行步骤

1. 查问多条步骤
  1. 调用db.Query()办法执行SQL语句,返回一个Rows查问后果。
  2. rows.Next()办法的返回值作为for循环的条件,迭代查问数据。
  3. 在循环中,通过 rows.Scan()办法读取每一行数据。
  4. 调用db.Close()敞开查问。
2.查问单条步骤
  1. 调用db.QueryRow()办法执行SQL语句,返回一个Row查问后果。
  2. 而后调用row.Scan()读取数据。

6.2 代码示例

package mainimport (    "database/sql"    "fmt"    _ "github.com/go-sql-driver/mysql")func main() {    // 连贯数据库    db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/nsbd_app?charset=utf8")    checkError(err)    //查问多条数据    rows := queryRows(db)    fmt.Printf("多条返回: \n%+v\n",rows)    // 查问单条数据    row := queryRow(db)    fmt.Printf("单条返回: \n%+v\n",row)}// 创立表的映射对象type User struct {    Uid        int    UserName   string    CreateTime int    Birthday   sql.NullString //有的值可能为NULL}//查问多条数据func queryRows(db *sql.DB) []User {    stmt, err := db.Prepare("select id,username,createtime,birthday from nsbd_user where id < ?")    checkError(err)    rows, err := stmt.Query(30)    // 提早敞开    defer rows.Close()    checkError(err)    user := new(User)    //users := make([]User,5)    var users []User    for rows.Next() {        // rows.Scan()办法的参数程序很重要,必须和查问后果的column绝对应(数量和程序都须要统一)        err := rows.Scan(&user.Uid, &user.UserName, &user.CreateTime, &user.Birthday)        checkError(err)        users = append(users, *user)    }    return users}// 查问单条数据func queryRow(db *sql.DB) User {    stmt, err := db.Prepare("select id,username,createtime,birthday from nsbd_user where id = ?")    checkError(err)    user := new(User)    err = stmt.QueryRow(4).Scan(&user.Uid, &user.UserName, &user.CreateTime, &user.Birthday)    checkError(err)    return *user}//检测谬误func checkError(err error) {    if err != nil {        panic("操作失败:" + err.Error())    }}

输入:

多条返回: [{Uid:1 UserName:admin CreateTime:0 Birthday:{String:2017-04-15 Valid:true}} {Uid:2 UserName:u2 CreateTime:1605858072 Birthday:{String:1993-02-14 Valid:true}} {Uid:3 UserName:u3 CreateTime:1606289644 Birthday:{String:1991-05-31 Valid:true}} {Uid:4 UserName:u4 CreateTime:1610521164 Birthday:{String:1989-11-24 Valid:true}} {Uid:5 UserName:u5 CreateTime:1610588359 Birthday:{String: Valid:false}}]单条返回: {Uid:4 UserName:u4 CreateTime:1610521164 Birthday:{String:1989-11-24 Valid:true}}

6.3 注意事项

  • rows.Scan()办法的参数程序很重要,必须和查问后果的column绝对应(数量和程序都须要统一);
  • Go是强类型语言,在查问数据时先定义数据类型,针对字段值为NULL时,数据类型应定义为:sql.NullString、sql.NullInt64等,并能够通过Valid值来判断查问到的值是赋值状态还是未赋值状态。
  • 每次db.Query()操作后,都倡议调用rows.Close()rows.Close()操作是幂等操作,即使对已敞开的rows再执行close()也没关系。

6.4 为什么查问后要敞开连贯?

因为db.Query()会从数据库连接池中获取一个连贯,这个底层连贯在后果集(rows)未敞开前会被标记为处于忙碌状态。当遍历读到最初一条记录时,会产生一个外部EOF谬误,主动调用rows.Close()。但如果出现异常,提前退出循环,rows不会敞开,连贯不会回到连接池中,连贯也不会敞开,则此连贯会始终被占用。因而通常应用defer rows.Close()来确保数据库连贯能够正确放回到连接池中。

本文由mdnice多平台公布